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) <mst@shadowcat.co.uk>
CONTRIBUTORS
Grinnz - Dan Book (cpan:DBOOK) <dbook@cpan.org>
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.