GSP
Quick Navigator

Search Site

Unix VPS
A - Starter
B - Basic
C - Preferred
D - Commercial
MPS - Dedicated
Previous VPSs
* Sign Up! *

Support
Contact Us
Online Help
Handbooks
Domain Status
Man Pages

FAQ
Virtual Servers
Pricing
Billing
Technical

Network
Facilities
Connectivity
Topology Map

Miscellaneous
Server Agreement
Year 2038
Credits
 

USA Flag

 

 

Man Pages
Promises::Cookbook::SynopsisBreakdown(3) User Contributed Perl Documentation Promises::Cookbook::SynopsisBreakdown(3)

Promises::Cookbook::SynopsisBreakdown - A breakdown of the SYNOPSIS section of Promises

version 0.94

  use AnyEvent::HTTP;
  use JSON::XS qw[ decode_json ];
  use Promises qw[ collect deferred ];

  sub fetch_it {
      my ($uri) = @_;
      my $d = deferred;
      http_get $uri => sub {
          my ($body, $headers) = @_;
          $headers->{Status} == 200
              ? $d->resolve( decode_json( $body ) )
              : $d->reject( $body )
      };
      $d->promise;
  }

  my $cv = AnyEvent->condvar;

  collect(
      fetch_it('http://rest.api.example.com/-/product/12345'),
      fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
      fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
  )->then(
      sub {
          my ($product, $suggestions, $reviews) = @_;
          $cv->send({
              product     => $product,
              suggestions => $suggestions,
              reviews     => $reviews,
          })
      },
      sub { $cv->croak( 'ERROR' ) }
  );

  my $all_product_info = $cv->recv;

The example in the synopsis actually demonstrates a number of the features of this module, this section will break down each part and explain them in order.

  sub fetch_it {
      my ($uri) = @_;
      my $d = deferred;
      http_get $uri => sub {
          my ($body, $headers) = @_;
          $headers->{Status} == 200
              ? $d->resolve( decode_json( $body ) )
              : $d->reject( $body )
      };
      $d->promise;
  }

First is the "fetch_it" function, the pattern within this function is the typical way in which you might wrap an async function call of some kind. The first thing we do it to create an instance of Promises::Deferred using the "deferred" function, this is the class which does the majority of the work or managing callbacks and the like. Then within the callback for our async function, we will call methods on the Promises::Deferred instance. In the case we first check the response headers to see if the request was a success, if so, then we call the "resolve" method and pass the decoded JSON to it. If the request failed, we then call the "reject" method and send back the data from the body. Finally we call the "promise" method and return the promise 'handle' for this deferred instance.

At this point out asynchronous operation will typically be in progress, but control has been returned to the rest of our program. Now, before we dive into the rest of the example, lets take a quick detour to look at what promises do. Take the following code for example:

  my $p = fetch_it('http://rest.api.example.com/-/user/bob@example.com');

At this point, our async operation is running, but we have not yet given it anything to do when the callback is fired. We will get to that shortly, but first lets look at what information we can get from the promise.

  $p->status;

Calling the "status" method will return a string representing the status of the promise. This will be either in progress, resolved, resolving (meaning it is in the process of resolving), rejected or rejecting (meaning it is in the process of rejecting). (NOTE: these are also constants on the Promises::Deferred class, "IN_PROGRESS", "RESOLVED", "REJECTED", etc., but they are also available as predicate methods in both the Promises::Deferred class and proxied in the Promises::Promise class). At this point, this method call is likely to return in progress. Next is the "result" method:

  $p->result;

which will give us back the values that are passed to either "resolve" or "reject" on the associated Promises::Deferred instance.

Now, one thing to keep in mind before we go any further is that our promise is really just a thin proxy over the associated Promises::Deferred instance, it stores no state itself, and when these methods are called on it, it simply forwards the call to the associated Promises::Deferred instance (which, as I said before, is where all the work is done).

So, now, lets actually do something with this promise. So as I said above the goal of the Promise pattern is to reduce the callback spaghetti that is often created with writing async code. This does not mean that we have no callbacks at all, we still need to have some kind of callback, the difference is all in how those callbacks are managed and how we can more easily go about providing some level of sequencing and control.

That all said, lets register a callback with our promise.

  $p->then(
      sub {
          my ($user) = @_;
          do_something_with_a_user( $user );
      },
      sub {
          my ($err) = @_;
          warn "An error was received : $err";
      }
  );

As you can see, we use the "then" method (again, keep in mind this is just proxying to the associated Promises::Deferred instance) and passed it two callbacks, the first is for the success case (if "resolve" has been called on our associated Promises::Deferred instance) and the second is the error case (if "reject" has been called on our associated Promises::Deferred instance). Both of these callbacks will receive the arguments that were passed to "resolve" or "reject" as their only arguments, as you might have guessed, these values are the same values you would get if you called "result" on the promise (assuming the async operation was completed).

It should be noted that the error callback is optional. If it is not specified then errors will be silently eaten (similar to a "try" block that has not "catch"). If there is a chain of promises however, the error will continue to bubble to the last promise in the chain and if there is an error callback there, it will be called. This allows you to concentrate error handling in the places where it makes the most sense, and ignore it where it doesn't make sense. As I alluded to above, this is very similar to nested "try/catch" blocks.

And really, that's all there is to it. You can continue to call "then" on a promise and it will continue to accumulate callbacks, which will be executed in FIFO order once a call is made to either "resolve" or "reject" on the associated Promises::Deferred instance. And in fact, it will even work after the async operation is complete. Meaning that if you call "then" and the async operation is already completed, your callback will be executed immediately.

So, now lets get back to our original example. I will briefly explain my usage of the AnyEvent "condvar", but I encourage you to review the docs for AnyEvent yourself if my explanation is not enough.

So, the idea behind my usage of the "condvar" is to provide a merge-point in my code at which point I want all the asynchronous operations to converge, after which I can resume normal synchronous programming (if I so choose). It provides a kind of a transaction wrapper if you will, around my async operations. So, first step is to actually create that "condvar".

  my $cv = AnyEvent->condvar;

Next, we jump back into the land of Promises. Now I am breaking apart the calling of "collect" and the subsequent chained "then" call here to help keep things in digestible chunks, but also to illustrate that "collect" just returns a promise (as you might have guessed anyway).

  my $p = collect(
      fetch_it('http://rest.api.example.com/-/product/12345'),
      fetch_it('http://rest.api.example.com/-/product/suggestions?for_sku=12345'),
      fetch_it('http://rest.api.example.com/-/product/reviews?for_sku=12345'),
  );

So, what is going on here is that we want to be able to run multiple async operations in parallel, but we need to wait for all of them to complete before we can move on, and "collect" gives us that ability. As we know from above, "fetch_it" is returning a promise, so obviously "collect" takes an array of promises as its parameters. As we said before "collect" also returns a promise, which is just a handle on a "Promises::Deferred" instance it created to watch and handle the multiple promises you passed it. Okay, so now lets move onto adding callbacks to our promise that "collect" returned to us.

  $p->then(
      sub {
          my ($product, $suggestions, $reviews) = @_;
          $cv->send({
              product     => $product,
              suggestions => $suggestions,
              reviews     => $reviews,
          })
      },
      sub { $cv->croak( 'ERROR' ) }
  );

So, you will notice that, as before, we provide a success and an error callback, but you might notice one slight difference in the success callback. It is actually being passed multiple arguments, these are the results of the three "fetch_it" calls passed into "collect", and yes, they are passed to the callback in the same order you passed them into "collect". So from here we jump back into the world of "condvars", and we call the "send" method and pass it our newly assembled set of collected product info. As I said above, "condvars" are a way of wrapping your async operations into a transaction like block, when code execution encounters a "recv", such as in our next line of code:

  my $all_product_info = $cv->recv;

the event loop will block until a corresponding "send" is called on the "condvar". While you are not required to pass arguments to "send" it will accept them and the will in turn be the return values of the corresponding "recv", which makes for an incredibly convenient means of passing data around your asynchronous program.

It is also worth noting the usage of the "croak" method on the "condvar" in the error callback. This is the preferred way of dealing with exceptions in AnyEvent because it will actually cause the exception to be thrown from "recv" and not somewhere deep within a callback.

And that is all of it, once "recv" returns, our program will go back to normal synchronous operation and we can do whatever it is we like with $all_product_info.

Stevan Little <stevan.little@iinteractive.com>

This software is copyright (c) 2014 by Infinity Interactive, Inc..

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

2014-12-28 perl v5.32.1

Search for    or go to Top of page |  Section 3 |  Main Index

Powered by GSP Visit the GSP FreeBSD Man Page Interface.
Output converted with ManDoc.