The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

AnyEvent::Promises - simple implementation of Promises/A+ spec

VERSION

version 0.06

SYNOPSIS

    use AnyEvent::Promises qw(deferred merge_promises);
    use AnyEvent::HTTP;
    use JSON qw(decode_json encode_json);

    sub wget {
        my ($uri) = @_;
        my $d = deferred;
        http_get $uri => sub {
            my ( $body, $headers ) = @_;
            $headers->{Status} == 200
                ? $d->resolve( decode_json($body) )
                : $d->reject('receiving data failed with status: '.  $headers->{Status} );
        };
        return $d->promise;
    }

    sub wput {
        my ($uri, $data) = @_;
        my $d = deferred;

        http_put $uri, body => encode_json($data) => sub {
            my ( $body, $headers ) = @_;
            $headers->{Status} == 200 || $headers->{Status} == 204
                ? $d->resolve( $body? decode_json($body) )
                : $d->reject('putting data failed with status: '.  $headers->{Status} );
        };
        return $d->promise;
    }

    my $cv = AnyEvent->condvar;
    merge_promises(
        wget('http://rest.api.example.com/customer/12345'),
        wget('http://rest.api.example.com/order/2345'),
        wget('http://rest.api.example.com/payment/3456')
    )->then(
        sub {
            my ($customer, $order, $payment) = @_;

            my $data = mix_together($customer, $order, $payment);
            return wput('http://rest2.api.example.com/aggregate/567', $data);
        }
    )->then(
        sub {
            # do something after the data are send
        },
        sub {
            # do something with the error
            # the error can be from wget as well as from wput 
        }
    );
        
    
    my $cv = AE::cv;
    # the condvar has to be finished somehow
    $cv->recv;

DESCRIPTION

AnyEvent::Promises is an implementation of the Promise pattern for asynchronous programming - see http://promises-aplus.github.io/promises-spec/.

Promises are the way how to structure your asynchronous code to avoid so called callback hell.

METHODS

There are two classes of objects - deferred objects and promise objects. Both classes are "private", the objects are created by calling functions from AnyEvent::Promises.

Typically a producer creates a deferred object, so it can resolve or reject it asynchronously while returning the consumer part of deferred object (the promise) synchronously to the consumer.

The consumer can synchronously "install handlers" on promise object to be notified when the underlying deferred object is resolved or rejected.

The deferred object is created via deferred function (see EXPORTS).

The promise object is typically created via $deferred->promise or $promise->then.

Methods of deferred (producers)

promise

Returns the promise for the deferred object.

resolve(@values)

Resolve the deferred object with values. The argument list may be empty.

reject($reason)

Reject the deferred object with a reason (exception). The $reason argument is required and must be true (in Perl sense).

A deferred object can be resolved or rejected once only. Any subsequent call of resolve or reject is silently ignored.

Methods of promise

The promise object is a consumer part of deferred object. Each promise has an underlying deferred object.

The promise is fulfilled when resolve was called on the underlying deferred object. The values of promise are simply the arguments of $deferred->resolve.

The promise is rejected when reject was called on the underlying deferred object. The reason of promise is simply the argument of $deferred->reject.

then($on_fulfilled, $on_rejected)

The basic method of a promise. This method returns a new promise.

Each of $on_fulfilled and $on_rejected arguments is either coderef or undef.

    my $pp = $p->then($on_fulfilled, $on_rejected);

The $pp is fulfilled or rejected after $p is fulfilled or rejected according to following rules:

If the $p is fulfilled and $on_fulfilled is not a coderef (it is undef, another value has no meaning), then $pp is fulfilled with the same values as $p.

If the $p is rejected and $on_rejected is not a coderef (it is undef, another value has no meaning), then $pp is rejected with the same reason as $p.

If the $p is fulfilled, then $on_fulfilled handler is called with the values of $p as an arguments.

If the $p is rejected, then $on_rejected handler is called with the rejection reason of $p as an argument.

The handler (either $on_fulfilled or $on_rejected) is called in a list context so it can return multiple values (here it differs from JavaScript implementation).

If the handler throws an exception, then $pp is rejected with the exception.

If the handler does not throw an exception and does not return a promise, then $pp is fulfilled with the values returned by the handler.

If the handler returns a promise, then $pp is fulfilled/rejected when the promise returned is fulfilled/rejected with the same values/reason.

It must be stressed that any handler is called outside of current stack in the "next tick" of even loop using AnyEvent->postpone. It implies that without an event loop running now or later the handler is never called.

See example:

    my $d = deferred();
    $d->resolve(10);
    my $p = $d->promise->then(sub { 2 * shift() });
    warn $p->state; # yields 'pending' because the handler is yet to be called 
    warn $p->value; # yield undef for the same reason

The behaviour of then in JavaScript is more precisely described here: http://promises-aplus.github.io/promises-spec/#the__method.

sync([$timeout])
    use AnyEvent::Promises qw(make_promise deferred);
    
    make_promise(8)->sync; # returns 8
    make_promise(sub { die "Oops" })->sync; # dies with Oops

    deferred()->promise->sync; # after 5 seconds dies with "TIMEOUT\n"
    deferred()->promise->sync(10); # after 10 seconds dies with "TIMEOUT\n"

Runs the promise synchronously. Runs new event loop which is finished after $timeout (default 5) seconds or when the promise gets fulfilled or rejected.

If the promise gets fulfilled before timeout, returns the values of the promise. If the promise gets rejected before timeout, dies with the reason of the promise. Otherwise dies with TIMEOUT string.

values

If the promise was fulfilled, returns the values the underlying deferred object was resolved with. If the promise was not fulfilled (was rejected or it is still pending), returns an empty list.

value

The first element from values the underlying deferred object was resolved with. If the promise was not fulfilled (was rejected or it is still pending), returns undef.

Having

    my $d = deferred();
    $d->resolve( 'a', 20 );
    my $p = $d->promise;
    $p->values;    # (returns ('a', 20))
    $p->value;     #  (returns 'a')
reason

If the promise was rejected, returns the reason the underlying deferred object was resolved with. If the promise was not rejected (was fulfilled or it is still pending) returns undef.

state

Returns either pending, fulfilled, rejected.

is_pending

Returns true when the promise was neither fulfilled nor rejected.

is_fulfilled

Returns true when the promise was fulfilled.

is_rejected

Returns true when the promise was rejected.

EXPORTS

All functions are exported on demand.

deferred()

Returns a new deferred object.

merge_promises(@promises)

Accepts a list of promises and returns a new promise.

After any of the promises is rejected, the resulting promise is rejected with the same reason as the first rejected promise.

After all of the promises are fulfilled, the resulting promise is fulfilled with values being the list of $promise->value in order they are passed to merge_promises

    my $d1 = deferred();
    my $d2 = deferred();
    my $d3 = deferred();
    $d1->resolve( 'A', 'B' );
    $d2->resolve;
    $d3->resolve( 3   );

    merge_promises( $d1->promise, $d2->promise, $d3->promise )->then(
        return @_;    # yields ('A', undef, 3)
    );

When called with empty list of promises returns promise which is resolved with empty list.

make_promise($arg)

Shortcut for creating promises.

If $arg is a promise, then make_promise returns it.

If $arg is a coderef, then make_promise is equivalent to:

    my $d = deferred();
    $d->resolve();
    $d->promise->then($arg);

otherwise it is an equivalent to:

    my $d = deferred();
    $d->resolve($arg);
    $d->promise;
is_promise($arg)

Returns true if the argument is a promise (object with method then).

SEE ALSO

AnyEvent

To use this module it is necessary to have basic understanding of AnyEvent event loop.

Promises

Although AnyEvent::Promises is similar to Promises (and you can use its more thorough documentation to understand the concept of promises) there are important differences.

AnyEvent::Promises does not work without running event loop based on AnyEvent. All $on_fulfilled, $on_rejected handlers (arguments of then method) are run in "next tick" of event loop as is required in 2.2.4 of the promises spec http://promises-aplus.github.io/promises-spec/#point-39.

There is also a crucial difference in $on_reject handler behaviour (exception handling). Look at

    my $d = deferred();
    $d->reject($reason);
    my $p = $d->promise->then(
        sub { },
        sub {
            return @_;
        }
    );

    $p->then(
        sub {
            warn "Code here is called when using AnyEvent::Promises";
        },
        sub {
            warn "Code here is called when using Promises";
        }
    );

With Promises the $p promise is finally rejected with $reason, while with AnyEvent::Promises the $promise is finally fulfilled with $reason, because the exception was handled (the handler did not throw an exception).

https://github.com/kriskowal/q/wiki/API-Reference

Here I shamelessly copied the ideas from.

AUTHOR

Roman Daniel <roman.daniel@davosro.cz>

COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Roman Daniel.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.