2013/12/07

Futures advent day 7

Day 7 - Failures with Values

Yesterday we saw how we can use an else block to catch an exception and make it return a different value, including a success. Of course, it was a rather blunt way to handle a failure because it just turned any failure into a 599 HTTP status.

This is perhaps a good time to explain that, like done can be passed a list of multiple values for on_done and then to receive, so too can fail. The first value in this list must be true and ought to be well-behaved as a string, because get will use it as the exception it will die() with if it has to. Any other values in the list are ignored by get, but are passed in to on_fail and else.

Lets now make a better version of GET_checked that passes the failing HTTP response along with the failure.

sub GET_checked
{
  my ( $url ) = @_;

  GET( $url )->then( sub {
    my ( $r ) = @_;
    if( $r->code !~ m/^[23]../ ) {
      return Future->new->fail( $r->code." ".$r->message, $r );
    }
    else {
      return Future->new->done( $r );
    }
  });
}

We can now handle our errors better by only masking one kind of error that we are interested in.

my $f = GET_checked("http://my-site-here.com/a-page")
  ->else( sub {
     my ( $failure, $response ) = @_;

     if( $response->code != 500 ) {
       return Future->new->fail( $failure, $response );
     }

     return Future->new->done(
       HTTP::Response->new( 200, "OK", [],
         "Server is down, but have some fluffy kittens instead")
     );
  });

my $response = $f->get;

Now if the server fails with a 500 error, we will think of fluffy kittens instead. But any other error is still reported as a real failure.

<< First | < Prev | Next >

2013/12/06

Futures advent day 6

Day 6 - Control Flow with Failures

We have now seen how done is used to mark a Future as successfully complete thus causing its get method to return, and how then is used to create a chain of activity between them when they succeed. We have also seen how fail can be used to mark a Future as having failed thus causing its get method to throw the exception. We can complete this set of methods by adding else, which is used to chain an activity after a Future if it fails.

my $f = GET("http://my-site-here.com/a-page")
  ->else( sub {
    return Future->new->done(
      HTTP::Response->new( 599, "Failed", [], "GET failed" )
    );
  });

my $response = $f->get;

Here we are using else to act as a kind of try/catch block, taking any exception and turning it into a successful response instead. If the GET future returns successfully, the else code doesn't get to run; instead the success gets passed right through to $f.

<< First | < Prev | Next >

2013/12/05

Futures advent day 5

Day 5 - Causing a Future to fail

Yesterday we looked at how to handle a Future that has failed. We can cause a Future to fail by invoking its fail method instead of done.

sub GET_checked
{
  my ( $url ) = @_;

  GET( $url )->then( sub {
    my ( $r ) = @_;
    if( $r->code !=~ m/^[23]../ ) {
      return Future->new->fail( $r->code." ".$r->message );
    }
    else {
      return Future->new->done( $r );
    }
  });
}

Here we have created a checked version of our hypothetical HTTP GET function, which returns a Future that will only be successful if the HTTP response was in the 2xx or 3xx ranges. If it gets an error (4xx or 5xx) then the Future will fail.

Another way to cause a future to fail is to simply throw a regular perl exception from a then or else code block. Each call to a code reference passed to these methods is wrapped in a eval {} block and causes the future to fail if the code throws an exception. This makes it easier to handle because now the chained future will fail, rather than causing the code that marked the preceding future as complete to propagate the exception it threw.

my $f = GET_checked("http://my-site-here.com/products.xml")
  ->then( sub {
    my ( $response ) = @_;
    if( $response->content_type ne "text/xml" ) {
      die "Expected Content-type: text/xml";
    }
    return Future->new->done( $response );
  });

This code is equivalent to code which uses Future->new->fail - the caller will not directly die with that exception, but instead the returned future will fail.

<< First | < Prev | Next >

2013/12/04

Futures advent day 4

Day 4 - Coping with failure

So far every Future example we have seen has resulted in an eventual success. But, like regular perl functions can either return a result or die() an exception, so too can Futures either complete with a result or a failure. Whereas the on_done method attaches code to handle a successful result we can use on_fail similarly to attach code to handle a failure.

my $f = GET("http://my-site-here.com/");

$f->on_done( sub {
  my ( $response ) = @_;
  print "Got a response\n";
});

$f->on_fail( sub {
  my ( $failure ) = @_;
  print STDERR "It failed: $failure\n";
});

If the get method receives a failure on the Future instead of a success, it throws the message from that failure as a perl exception, causing the method to die instead of return. This leads to many cases of Future code being able to look and act very similarly to regular perl function calls, with values being returned, or exceptions being thrown and caught.

<< First | < Prev | Next >

2013/12/03

Futures advent day 3

Day 3 - Chaining Futures to perform a sequence of actions

A more powerful ability than on_done, and one which in practice turns out to be used much more often, is provided by the then method. This method itself returns a Future, and expects code that will return a Future when it is invoked. In this way it provides an ability to perform a second action which returns a Future after the first one completes, and returns a Future that represents the complete combination of the first and the second.

my $f = GET("http://my-site-here.com/first")
  ->then( sub {
    my ( $first_response ) = @_;
    my $path = path_from_response($first_response);
    GET("http://my-site-here.com/$path);
  });

my $second_response = $f->get;

In this second example after the first page has been returned we then fetch a second page by somehow using the result of the first page's response to give a path name for the second. The returned Future in $f will be complete after this second response has been received. The call to get will then wait for this to happen.

Of course, we are not limited to simply two actions - because the then method returns another Future, we can simply call then on that as well to chain as many steps of a process as are necessary to complete it. This leads to a neat sequence of code, quite unlike the ever-indenting nature of passing callback functions.

<< First | < Prev | Next >

2013/12/02

Futures advent day 2

Day 2 - Doing something when a Future completes

Because a Future object represents an operation that is currently in progress or has finished it provides the ideal place to attach logic to ask for further activity to happen when the original has completed. The most immediate way is to pass a piece of code in a sub reference to the on_done method.

my $f = GET("http://my-site-here.com/");

$f->on_done( sub {
  my ( $response ) = @_;
  print "Got a response\n";
});

In this case, as for many posts yet to come, we are presuming some hypothetical GET function that returns a Future wrapping an HTTP operation in the obvious manner.

If the returned Future is already complete (perhaps because it is a synchronous client that always completes immediately, or because it was served internally from a cache) then the on_done method invokes the code immediately. If not, the code is stored inside the Future object itself for when it eventually gets its result.

<< First | < Prev | Next >

2013/12/01

Futures advent day 1

It is traditional around this time of year for Perl blogs to publish an advent calendar - a series of 24 short little posts around a common theme.

People have suggested I might write one about Futures, so here goes...

Day 1 - Futures can return values synchronously

You don't in fact have to use a Future for anything asynchronous. A simple synchronous-returning function can use them too.

use Future;

sub sum
{
   my @numbers = @_;
   my $total = 0;
   $total += $_ for @numbers;
   return Future->new->done( $total );
}

say "The total is " . sum( 10, 20, 30 )->get;

It may not be immediately obvious currently why you want to do this, but I hope to motivate why over the following 23 posts...

<< First | < Prev | Next >