7
\$\begingroup\$

First time poster, be gentle.

I wanted something similar to Mojo::Promise->all, except with an option to discard certain rejected (or, I guess, resolved) promises dependent on rejection value, as determined by caller.

Only using this internally, so nevermind my polluting the Mojo::Promise:: namespace.

package Mojo::Promise::Filter;
use Moose;

extends 'Mojo::Promise';

has 'promises' => (
    isa => 'ArrayRef[Mojo::Promise]',
    is => 'ro',
    required => 1,
);

has 'resolve_filter' => ( # truthy return means *DO* filter out
    isa => 'CodeRef',
    is => 'ro',
    required => 1,
    lazy => 1,
    default => sub { return sub {}; },
);

has 'reject_filter' => ( # truthy return means *DO* filter out
    isa => 'CodeRef',
    is => 'ro',
    required => 1,
    lazy => 1,
    default => sub { return sub {}; },
);

has '_promises_state_tracker' => (
    # numbered hashref whose primary keys correspond to passed promise-array's indices
    # secondary keys are flags for whether each original promise filtered
    # and whether promise resolved/rejected,
    # plus the original promise itself, e.g. : 
    # { 0 => { filtered => (undef|1), ror => (undef|1), original_promise => (Mojo::Promise) } }
    isa => 'HashRef[HashRef]',
    is => 'ro',
    lazy => 1,
    builder => '_build_promises_state_tracker',
);


sub _build_promises_state_tracker {
    my $self = shift;
    my $i = 0;
    return { map { $i++ => { original_promise => $_ } } @{$self->promises} };
}

sub _resolve_or_reject_self_if_last_child {
    my $self = shift;

    my @ror = grep { $_->{ror} } values %{$self->_promises_state_tracker};
        # gather the thus-far resolved-or-rejected promises
    return unless scalar keys %{$self->_promises_state_tracker} == scalar @ror;
        # return unless we have as many ror promises as were passed in

    my @unfiltered = map { $_->{original_promise} } grep { !$_->{filtered} } values %{$self->_promises_state_tracker};
    return Mojo::Promise->all(@unfiltered)->then(sub {
        $self->resolve(@_)
    })->catch(sub {
        $self->reject(@_)
    });
}

sub filter {
    my $self = shift;

    for my $i (0 .. $#{$self->promises}) {
        $self->promises->[$i]->then(sub {
            $self->_promises_state_tracker->{$i}->{filtered} = 1
                if $self->resolve_filter->(@_);
        })->catch(sub {
            $self->_promises_state_tracker->{$i}->{filtered} = 1
                if $self->reject_filter->(@_);
        })->finally(sub {
            $self->_promises_state_tracker->{$i}->{ror} = 1;
                # ror = Resolved Or Rejected
            $self->_resolve_or_reject_self_if_last_child();
        });
    }

    return $self;
}

no Moose;

1;

Use as :

my $re_filter = qr/some special text/;
my $reject_filter = sub {
        my $v = shift;
        return 1 if ref \$v eq 'SCALAR' && $v =~ $re_filter;
    };

my $p = Mojo::Promise::Filter->new(
        reject_filter => $reject_filter,
        promises => \@promises
    );

return $p->filter;
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

I'm not entirely sure I understand what this does, but here goes.

  • Mojolicious doesn't use Moose. I would think long and hard whether I'd extend a Mojo::Base class with Moose. Does this work properly?
  • There are several instances of required together with default and lazy in your new attributes. Those do not make sense together. If it's required it needs to be passed in at construction. There is no need to make it lazy then as it will always be there from the start.
  • Your default for both reject_filter as well as resolve_filter are subs that return undef implicitly. That means if you don't pass those in, everything is discarded. Is that the intended behavior?
  • Your class is a subclass of Mojo::Promise, so there is no need to call Mojo::Promise->all. It's got an inherited ->all method that you can call on $self. (Mojo::Promise->all) returns a new object, so this breaks.
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Thanks ! I'm already using Moose elsewhere in my Mojo app, so I figured it wouldn't incur a significant overhead to use here. I trust that Moose handles extending a non-Moose class well enough, though I'll admit I don't know the subtle ways in which things might act quirky. I will look into "MooseX::NonMoose", or maybe not using Moose at all. Good catch with required+lazy. Yes, a default of "sub {}" returning undef means default behviour is for no promises to be filtered out. I think you might be right about being able to call just "$self->all(@unfiltered)", I will test it out. Thanks again ! \$\endgroup\$
    – robut
    Jun 7, 2018 at 16:00
  • \$\begingroup\$ I wasn't thinking about the overhead regarding Moose, just that Mojolicious goes out of its way to not have Moose while maintaining a similar interface. I don't know what kind of things might happen there. Just wanted to note that down. \$\endgroup\$
    – simbabque
    Jun 7, 2018 at 16:02
  • \$\begingroup\$ I tried switching Mojo::Promise->all to $self->all, but it broke. Maybe related to the fact that method "all" "[r]eturns a new Mojo::Promise object [...]", per docs ? Or maybe because in the source for Mojo::Promise->all, it calls $class->new;, but $class is now Mojo::Promise::Filter and I told Moose to require attribute "promises", so "all" method fails internally to construct a new object ? Hm. Does Mojolicious offer a similar type system to Moose's ? I like being able to have Moose enforce, say, "isa => 'HashRef[HashRef]'". Compatibility vouch : stackoverflow.com/a/30809096 \$\endgroup\$
    – robut
    Jun 7, 2018 at 16:38
  • \$\begingroup\$ @robut as far as I know it doesn't have any types. It's trying to be simple and on the point. My advice on the all was a guess based on the class hierarchy. Sorry that that didn't work. \$\endgroup\$
    – simbabque
    Jun 8, 2018 at 8:25
2
\$\begingroup\$

Mojolicious has roles already.

 package Mojo::Promise::Role::Filter {
     use Mojo::Base '-role';
     ...; # stuff you want in the Promise class
     }

 my $promise->with_roles( '+Filter' )->new;
 ...;

See, for instance, Mojo::Promise::Role::Any.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.