2020/12/02

2020 Perl Advent Calendar - Day 2

<< First | < Prev | Next >

Yesterday we got our first look at the async/await syntax in a couple of simple examples. We saw how a function can be declared with async sub, and how a toplevel await expression can be used to invoke it and obtain its return value. So far we have only ever seen the await keyword being used directly on a call to an async sub, but that isn't the only way it can be used. To understand more fully what is going on here we need to take a look inbetween the two. Lets now begin to explore, in more detail, what these two keywords do individually.

The async part of the function definition changes the way its return value works. From the perspective of an outside caller it appears that any async sub always returns an instance of the Future class. Any value that the body of the function tries to return using its own return statement in fact becomes the result stored into that Future instance.

use Future::AsyncAwait;

async sub sum
{
    my @numbers = @_;
    my $total = 0;
    $total += $_ for @numbers;
    return $total;
}

my $f = sum(30, 40);

say "The future is ", $f;
say "The future is done" if $f->is_done;
say "The result of the future is ", $f->result;

# outputs:
The future is Future=HASH(0x5653a9bf4440)
The future is done
The result of the future is 70

Where the async keyword can be used to create a new future when the function is called, the await keyword is used to consume one and yield the result value stored inside it. As we have already seen, calling an async sub is one way to obtain such a Future instance, but any expression containing a future - such as a simple variable - can also be used.

use Future::AsyncAwait;

my $f = Future->done(50 + 60);

say "The result is ", await $f;

# outputs
The result is 110

So far these examples haven't been very interesting, because they all yield their result immediately. In order to better see what this new syntax provides for us, lets think again about the HTTP GET function we saw yesterday. To perform its task, this function will have to first send an HTTP request to a server, then wait for a response to be sent back from the server. It is this waiting part, that the Future instance represents.

We'll explore in more detail how the GET function can work in a later post, but suffice it for now to observe that by the time the function returns a future, it doesn't yet need to have received a response from the server. It can be waiting for that to arrive, and only later set the result of the future. After the GET function returns with this still-pending future, we have control once more and we can do something else while we wait for that result - perhaps send a second request.

use Future::AsyncAwait;

my $f1 = GET("http://my-site-here.com/page1");
my $f2 = GET("http://my-site-here.com/page2");

my $response1 = await $f1;
my $response2 = await $f2;

printf "page1 contains %d bytes; page2 contains %d bytes\n",
    length $response1->body, length $response2->body;

By calling the GET function twice and storing the futures in two variables, we have been able to start two concurrent requests, so we can wait for both of them to respond. Overall this means the program takes less time to complete, because it has been able to keep more than one thing up in the air at once. In effect, the act of calling an asynchronous function merely requests that it begin to perform its work. It isn't until we try to await on its future that we request that it must now finish, waiting for it if necessary. This is in contrast to calling a regular synchronous function, which must entirely complete its task before it can return.

This ability to start and manage possibly-multiple concurrent operations before requiring any of them to be finished is the central theme of what asynchronous programming with futures is all about. async/await syntax provides us many abilities to write asynchronous code on futures in a familiar way, which looks and feels much more like the more traditional synchronous style we have been used to. This is what we will explore over the next several days.

<< First | < Prev | Next >

1 comment: