How Perl programmers efficiently manage asynchronous program flows

Wrong Mistakes

But, let me get back to the nested client code: The Pyramid of Doom won't correctly process any errors that might occur. What happens, for example, if the response comes back with an error in the second step? The first-stage callback jumps to its callback on successful completion of the web request and asks the event loop to issue a further request. If this request fails, it is difficult to reach the context of the first stage or even the original program, which actually needs to hear about the problem and print error diagnostics and determine the root cause.

Or, how can the main program discover whether all the results from parallel remote web requests are in place or if a few are still missing? How can you interrupt the program flow, if a request fails, and ignore the requests that follow? You still need control over the program flow, even in asynchronous programming; it is this exact problem that JavaScript programmers have attempted to solve in recent years with several competing approaches, standing on the shoulders of giants who came up with it 30 years ago in functional languages.

PubSub

Software components that rely on sending and receiving events to communicate with one another can knock down the Pyramid of Doom. The approach relies on a publish/subscribe (PubSub) model, in which objects demonstrate their interest in incoming events and, if they receive them, launch methods. They additionally offer methods that allow other objects to send events to them.

The whole thing runs asynchronously, because an event that is sent won't be processed by the target object immediately  – only when it finds time to do so. Also, the event emitter does not synchronously wait for a result to be sent back immediately; rather, it returns to its own affairs and, while doing so, watches out for an incoming event that contains the results of the request.

The CPAN Object::Event module offers a simple implementation of this procedure for Perl. A class inherits from Object::Event and then uses the reg_cb() (for "register callback") method to register a callback for a given event. In addition, this also provides the inherited event() method, called by external objects that seek to trigger actions in the object of their desire.

Implementing the PubSub model, Listing 3 first defines a WgetPubSub class whose objects register a callback for the request event in the constructor. Upon receiving an event, they then retrieve the URL that arrives with the event payload by contacting the web server and then use a new event named response to return the result once it has been received. For the chained web requests to happen in sequence, line 40 uses reg_cb() to define a callback for response. When it gets the result of the first request, it creates another PubSub object, $wget2, to initiate a new web request.

Listing 3

http-get-pubsub

 

The code of the main program is not nested, but linear, which improves readability. Also, it is up to the programmer to send individual PubSub objects to other functions, or even third-party packages, for further processing, if this is useful to create a logical division.

PubSub also makes error handling easier. Because the second stage of the web request can easily use the $wget variable to access the web agent in the first stage, it is just as easy to send a message in case of error, for example, with an event named error, carrying the error message or code.

If the component is listening for these events, it notices that a problem has occurred downstream and, in turn, can notify a component in the main program.

Empty Promises

Promises [3] are a promising (sic!) new method for asynchronous programming. An asynchronous function with a callback returns an abstract object of the class Promises – a kind of window into the future. It is initially unknown whether the promise will ever receive a result or just cause an error. Until an event happens somewhere else, the promise is a kind of externally controlled, hybrid being, waiting for someone to make a decision, pull a switch, and feed in any resulting data.

The easiest way to explain how a promise works is to look at an example. Line 5 of Listing 4 defines a deferred object – a kind of promise with decision-making power. Line 7 derives a promise from this, and the then() method assigns two alternative states to the promise: Schrödinger's cat is alive in one case and dead in the other. The two are obviously incompatible; only one can come true at some point.

Listing 4

promise-cat

 

The decision is made at the moment at which the parent Deferred object calls one of two methods: If it calls resolve(), the first state applies, if it calls reject(), the second state becomes a reality. Once the switch has been pulled, there is no turning back; the status of the promise is then fixed.

The difference between a Deferred and a Promise only relates to accessing the reject() and resolve() methods. A Deferred can trigger the methods and thus decide the fate of the promise derived from it. A Promise can only respond; it is not free to decide.

In the case of an asynchronous function that returns a promise, no decision has initially been made on whether you will see a result or an error message. The switch for the decision is pulled by the callback when data arrives or an error occurs. This only happens later on, however, after the current program flow has long passed control back to the event loop. This is why a promise happily takes its instructions from the main program after it has been returned by the asynchronous function; at this point, no results exist yet and no one has had a chance since to call the resolve or the reject callbacks.

Listing 5 converts the http_get() function from the AnyEvent module collection into a function named fetch_url() in line 10, which expects a URL and returns a Promise. The callback, which http_get() calls if web data emerges or an error occurs, sets the switch with resolve() or reject().

Listing 5

http-get-promise

 

Line 26 uses the then() method to define the callback for a successful web request and leverages the property of Promises as per the latest promise/A+ specification [4], which stipulates that such a callback can then return a Promise. The $prom2 grabs this and enters the third round as of line 32.

The output from Listing 5 is again identical to that of Listings 1 and 3; all three scripts query the same URLs and get the same responses from the server. Because the resolve or reject callbacks, and thus the then() method, in turn return a promise, the chain of requests can even be implemented as follows without temporary variables:

->then( sub { # success
 } )
->then( sub { # success
 } )-> [...]

The Promises module, programmed exactly according to the specification, then makes sure that it processes the whole chain and immediately stops if an error with a reject call occurs in one link.

In this example, the code is easier to read than in the original, nested callback pyramid. Once again, a modern programming technique from a completely different language has found its way back into Good Old Perl.

Mike Schilli

Mike Schilli works as a software engineer with Yahoo! in Sunnyvale, California. He can be contacted at mailto:mschilli@perlmeister.com. Mike's homepage can be found at http://perlmeister.com.

Infos

  1. Burnham, Trevor. Async JavaScript: Build More Responsive Apps with Less Code: Pragmatic Express, 2012
  2. Listings for this article: ftp://linux-magazin.com/pub/listings/magazine/170
  3. "You're missing the point of promises" by Domenic Denicola: https://blog.domenic.me/youre-missing-the-point-of-promises/
  4. Promises/A+ specification: https://promisesaplus.com

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • WS-Addressing

    WS-Addressing is a standard that enables flexible communication between web services.

  • Programming Snapshot – Multilingual Programming

    We show you how to whip up a script that pulls an HTTP document off the web and how to find out which language offers the easiest approach.

  • AJAX

    AJAX offers a fast and efficient approach for building interactive websites. We’ll show you how to call upon the powers of AJAX for your own web creations.

  • WebRTC Protocol

    The WebRTC protocol converts your web browser into a communications center, supporting video chat over a peer-to-peer connection without the need for helper apps or browser plugins.

  • Perl: Network Monitoring

    To discover possibly undesirable arrivals and departures on their networks, a Perl daemon periodically stores the data from Nmap scans and passes them on to Nagios via a built-in web interface.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News