NAME
Async::Methods - Namespaced sugar methods for async/await and
future/promise based code
SYNOPSIS
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
# Normal synchronous code
print $ua->get('
http://trout.me.uk/')->result->body;
# Equivalent code running synchronously atop promises
print $ua->get_p('
http://trout.me.uk')->then::result->await::body;
# Equivalent code within an async subroutine
use Mojo::Base -async_await, -signatures;
async sub fetch ($url) {
await $ua->get_p($url)->then::result->then::body;
}
print fetch($url)->await::this;
DESCRIPTION
Async::Methods provides a set of helper methods operating via namespace
that make chaining together asynchronous methods easier. This is not at
all meant to be a replacement for the "async" and "await" keywords
available via Future::AsyncAwait or the "-async_await" flag to
Mojo::Base and in fact is largely meant to be used *with* such
facilities.
Note that in the following code I use $p for example variables but they
can be Future or Mojo::Promise objects or (hopefully) objects of any
other class that provides a similar interface.
Note that methods of each type provided can be called three ways:
$obj->the_type::some_method(@args);
will call "some_method" on a relevant object, and is effectively simply
sugar for the second type,
$obj->the_type::_(some_method => @args);
which calls the method name given in its first argument (yes, this means
that you can't use the first syntax to call a method called "_" but the
author of this module strongly suspects that won't be an inconvience in
most cases).
Thirdly, to match perl's capacity to allow <$obj->$cb(@args)> as a
syntax, you can also call:
$obj->the_type::_(sub { ... } => @args);
$obj->the_type::_($cb => @args);
to call that code reference as a method.
METHODS
start::
my $p = $obj->start::some_method(@args);
my $p = $obj->start::_(some_method => @args);
my $p = $obj->start::_(sub { ... } => @args);
"start::" methods don't do anything special in and of themselves but
register the $obj with Async::Methods to allow "catch::" and "else::" to
work correctly (see their documentation below for why you might find
that useful). Other than the registration part, this is entirely
equivalent to
my $p = $obj->some_method(@args);
then::
my $then_p = $p->then::some_method(@args);
my $then_p = $p->then::_(some_method => @args);
my $then_p = $p->then::_(sub { ... } => @args);
"then::" allows for chaining an additional method call from the return
value of the previous promise (assuming it's successful). As such, on
its own this is equivalent to
my $then_p = $p->then(
sub ($obj, @rest) { $obj->some_method(@args, @rest)) }
);
Note that "then::" does not require anything special of the promise upon
which it's called to provide the base functionality, but *does* need to
be called on the result of something rooted in "start::" if you want to
be able to chain "else::" or "catch::" from the return value.
else::
my $else_p = $p->else::some_method(@args);
my $else_p = $p->else::_(some_method => @args);
my $else_p = $p->else::_(sub { ... } => @args);
"else::" must be called on the result of a "start::" chained to a
"then::", and provides a callback if the start::ed method fails, invoked
on the *original* invocant. This makes it the "other half" of
Async::Methods' support for two-arg "<-"then>>, so:
my $else_p = $obj->start::one(@args1)
->then::two(@args2)
->else::three(@args3);
is functionally equivalent to:
my $else_p = $obj->one(@args1)
->then(
sub ($then_obj, @then_rest) {
$then_obj->two(@args2, @then_rest)
},
sub (@error) {
$obj->three(@args3, @error)
},
);
which the author hopes explains why you might, on the whole, not really
mind being forced to type start::.
Note that because "else::" always resolves to the second argument to a
two-arg "then" call, it can't be used in isolation. Fortunately, we
already provide "catch::" for that, which is documented next.
catch::
my $catch_p = $p->catch::some_method(@args);
my $catch_p = $p->catch::_(some_method => @args);
my $catch_p = $p->catch::_(sub { ... } => @args);
"catch::" can be called on the result of either a "start::" call or a
"start::" -> "then::" chain, and will catch any/all errors produced up
to this point, as opposed to "else::" which catches errors *before* the
preceding "then::" call.
As such, morally equivalent to:
my $catch_p = $obj->start::whatever(...)
->catch(sub ($obj, @error) {
$obj->some_method(@args, @error)
});
await::
my $ret = $p->await::this;
"await::this" is simple generic sugar for (at top level of your code
outside of an already-running event loop) spinning the event loop until
the promise completes and then either returning the result on success or
"die()"ing with the error on failure. For a future, it's equivalent to
my $ret = $f->get;
but if called on a Mojo::Promise loads Mojo::Promise::Role::Get and uses
that to complete the operation, so "await::this" can be called on either
and still provides a uniform interface. Assuming you install
Mojo::Promise::Role::Get if you need it of course - otherwise you'll get
an exception from the relevant "require" call.
my $ret = $p->await::some_method(@args);
my $ret = $p->await::_(some_method => @args);
my $ret = $p->await::_(sub { ... } => @args);
"await::" requires absolutely nothing of the promise upon which it's
called, and other than the special case of "this" is equivalent to
my $ret = $p->then::some_method(@args)->await::this;
Hopefully obvious caveat: If you want to await a method called "this"
you'll need to call one of
my $ret = $p->then::this(@args)->await::this;
my $ret = $p->await::_(this => @args);
but "this" did not strike the author as a sufficiently common method
name to be a deal-breaker in practice.
AUTHOR
mst - Matt S. Trout (cpan:MSTROUT) <
[email protected]>
CONTRIBUTORS
Grinnz - Dan Book (cpan:DBOOK) <
[email protected]>
COPYRIGHT
Copyright (c) 2020 the Async::Methods "AUTHOR" and "CONTRIBUTORS" as
listed above.
LICENSE
This library is free software and may be distributed under the same
terms as perl itself.