The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

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.