Async-Trampoline

 view release on metacpan or  search on metacpan

lib/Async/Trampoline.pm  view on Meta::CPAN

        return $items if not $i;
        push @$items, $i--;
        return loop($items, $i);  # may lead to deep recursion!
    }

    my $items = loop([], 5);

=for test
    is "@$items", "5 4 3 2 1", q(Synchronous/recursive);

Async/recursive:

    sub loop_async {
        my ($items, $i) = @_;
        return async_value $items if not $i;
        push @$items, $i--;
        return async { loop_async($items, $i) };
    }

    my $items = loop_async([], 5)->run_until_completion;

=for test
    is "@$items", "5 4 3 2 1", q(Async/recursive);

Async/generators:

    sub loop_gen {
        my ($i) = @_;
        return async_cancel if not $i;
        return async_yield async_value($i) => sub {
            return loop_gen($i - 1);
        };
    }

    my $items = loop_gen(5)->gen_collect->run_until_completion;

=for test
    is "@$items", "5 4 3 2 1", q(Async/generators);

=head1 ASYNC STATES

Each Async exists in one of these states:

=for test ignore

    Async
    +-- Incomplete
        +-- ... (internal)
    +-- Complete
        +-- Cancelled
        +-- Resolved
            +-- Error
            +-- Value

=for test

In B<Incomplete> states, the Async will be processed in the future.
At some point, the Async will transition to a completed state.

In C<async> and C<await> callbacks,
the Async will be updated to the state of the return value of that callback.

B<Completed> states are terminal.
The Asyncs are not subject to further processing.

A B<Cancelled> Async represents an aborted computation.
They have no value.
Cancellation is not an error,
but C<run_until_completion()> will die when the Async was cancelled.
You can cancel a computation via the C<async_cancel> constructor.
Cancellation is useful to abort loops,
or to fall back to an alternative with
C<< $may_cancel->resolved_or($alternative) >>.

B<Resolved> Asyncs are Completed Asyncs that finished their computation
and have a value, either an Error or a Value upon success.

An B<Error> Async indicates that a runtime error occurred.
Error Asyncs can be created with the C<async_error> constructor,
or when a callback throws.
The exception will be rethrown by C<run_until_completion()>.

A B<Value> Async contains a list of Perl values.
They can be created with the C<async_value> constructor.
The values will be returned by C<run_until_completion()>.
To access the values of an Async, you can C<await> it.

=head1 CREATING ASYNCS

=head2 async

    $async = async { ... };

Create an Incomplete Async with a code block.
The callback must return an Async.
When the Async is evaluated,
this Async is updated to the state of the returned Async.

=head2 async_value

    $async = async_value @values;

Create a Value Async containing a list of values.
Use this to return values from an Async callback.

=head2 async_error

    $async = async_error $error;

Create an Error Async with the specified error.
The error may be a string or error object.
Use this to fail an Async.
Alternatively, you can C<die()> inside the Async callback.

=head2 async_cancel

    $async = async_cancel;

Create a Cancelled Async.
Use this to abort an Async without using an error.

=head1 COMBINING ASYNCS

=head2 await

=for test
    $dependency = async { async_value 1,2, 3 };
    @dependencies = (async_value(1), async_value(), async_value(3));

    $async = $dependency->await(sub {
        my (@result) = @_;
        # ...
        return $new_async;
    });

    $async = await $dependency => sub {
        my (@result) = @_;
        # ...
        return $new_async;
    };

    $async = await [@dependencies] => sub {
        my (@results) = @_;
        # ...
        return $new_async;
    };

Wait until the C<$dependency> or C<@dependencies> Asyncs have a value,
then call the callback with the values as arguments.
If a dependency was cancelled or has an error,
the async is updated to that state.
The callback must return an Async.
Use this to chain Asyncs.
It does not directly return the values.

=head2 resolved_or

=head2 value_or

=for test
    $first_async = async { async_value };
    $alternative_async = async { async_value };
    $second_async = $alternative_async;

    $async = $first_async->resolved_or($alternative_async);
    $async = $first_async->value_or($alternative_async);

Evaluate the C<$first_async>.
Upon success, the Async is updated to the state of the C<$first_async>.
On failure, the C<$second_async> is evaluated instead.
This creates a new Async that will be updated
when the dependencies become available.

B<resolved_or> succeeds on Value or Error, and fails on Cancelled.
Use this as a fallback against cancellation.

B<value_or> only succeeds on Value, and fails on Cancelled or Error.
Use this as a try-catch to provide default values upon errors.

=head2 complete_then

=head2 resolved_then

=head2 value_then

    $async = $first_async->complete_then($second_async);
    $async = $first_async->resolved_then($second_async);
    $async = $first_async->value_then($second_async);

Evaluate the C<$first_async>.
Upon success, the C<$second_async> is evaluated.
On failure, the Async is updated to the state of the C<$first_async>.
This creates a new Async that will be updated
when the dependencies become available.

B<complete_then> always succeeds (Cancelled, Error, Value).

B<resolved_then> succeeds on Error or Value, and fails on Cancelled.

B<value_then> succeeds on Value, and fails on Cancelled or Error.
With regards to error propagation,
C<< $x->value_then($y) >>
behaves just like
C<< $x->await(sub { return $y } >>.

Use these functions to sequence actions on success and discarding their value.
They are like a semicolon C<;> in Perl,
but with different levels of error propagation.
You may want to sequence Asyncs if any Async causes side effects.

=head2 concat

    $async = $first_async->concat($second_async);

If both asyncs evaluate to Values, concatenate the values.

B<Example>:

    $async = (async_value 1, 2, 3)->concat(async_value 4, 5);
    #=> async_value 1, 2, 3, 4, 5

=for test {
    my @result = $async->run_until_completion;
    is "@result", "1 2 3 4 5", q(concat());
}

=head1 GENERATORS

A B<Generator> describes an Async
that has a continuation Async as its first value.
This continuation can be awaited to get the next continuation + value.
If the generator is Cancelled, no further items are available.
Errors are propagated.

Generators are useful for yielding a stream of values.

You can use C<async_yield()> to conveniently return a value with a continuation.
The C<gen_*()> Async methods can process generator streams.
They will fail at runtime when the Async is not a valid generator.

The most flexible way to handle generators is to C<await()> them.
However, many use cases are better served by more specialized functions.

B<Example:> a count down generator:

    sub count_down_generator {
        my ($i) = @_;
        return async_cancel if $i < 0;
        return async_yield async_value($i) => sub {
            return count_down_generator($i - 1);
        };
    }



( run in 2.468 seconds using v1.01-cache-2.11-cpan-437f7b0c052 )