Day 12 - Asynchronous Await
Up until now in these posts I have been deliberately vague on the subject of how the HTTP GET function actually works. All I have implied is that it takes a page URL and returns a Future that will eventually yield an HTTP::Response containing the resource. As I said right back in the first post on day 1, Futures work just fine if every result is in fact a synchronous return. Thus, we could choose to implement an entirely synchronous version of this function using LWP::UserAgent (and taking care not to confuse its get method with the unrelated Future one):
use Future; use LWP::UserAgent; my $ua = LWP::UserAgent; sub GET { my ( $url ) = @_; Future->call( sub { Future->wrap( $ua->get( $url ) ) }); }
Here we have used Future->wrap to conveniently create a future to contain the result of a successful call to the UserAgent's get method (remember, this is HTTP GET and unrelated to the future get method). This call is itself wrapped in a Future->call block to ensure that if the UserAgent throws an exception, this will be wrapped in a failed future.
Alternatively, if we wanted some level of asynchronous behaviour, because we wish to perform multiple concurrent actions, or mix this with other code, we could instead use Net::Async::HTTP which already provides a GET method having the semantics we want:
use IO::Async::Loop; use Net::Async::HTTP; my $loop = IO::Async::Loop->new; my $http = Net::Async::HTTP->new; $loop->add( $http ); sub GET { my ( $url ) = @_; return $http->GET( $url ); }
This one is implemented internally by Net::Async::HTTP returning a subclass of Future provided by IO::Async itself. This subclass understands how to wait for futures that are not yet ready, by invoking the containing loop until the result is available. Other event systems can be similarly catered for by subclassing Future to provide a suitable await method, which is used by get. We could even, if we were inclined towards threads, implement a subclass of Future which used some kind of thread-based synchronisation and communication to await the result being supplied by code running in a different thread.
Because they both conform to the interface of "returning a Future", either of these above implementations of GET are suitable for any of the examples we have seen so far, or will see in the next examples to come. So too would any other implementation that provides this interface. Because of this we find that Futures provide a powerful way to write the intermediate layers of processing and "business logic" in application libraries, which can remain agnostic on such low-level details as what event system is being used, or even if one is being used at all.
Why do we need both 'call' and 'wrap', and how it would look like without those convenience methods?
ReplyDeleteOh we don't really.. they are both fairly simple wrappers around the more basic methods. They just help express common patterns more cleanly.
ReplyDeleteFuture->call( sub { CODE } )
is the same as
my $f; eval { $f = CODE; 1 } or return Future->new->fail( $@ ); return $f;
and
Future->wrap( @VALUES )
is the same as
if( @VALUES == 1 and blessed $VALUES[0] and $VALUES[0]->isa("Future") ) { return $VALUES[0] } else { return Future->new->done( @VALUES ) }
The particular example above then would look more like
sub GET
{
my ( $url ) = @_;
my $f;
eval {
$f = Future->new->done( $ua->get( $url ) );
1;
} or return Future->new->fail( $@ );
return $f;
}
It's just somewhat harder to see the logic in there, for all the boilerplate noise.
Ugh. That comment doesn't preserve formatting at all :( Hopefully the intent is clear enough.
DeleteNice bllog you have
ReplyDelete