Hey, "recursive deferred promises" might sound like a smashingly complex idea, but really, it isn't. Here's the what, why and how.

The promises pattern is very useful for asynchronous code which tends to lead to "arrow-head" formation in code structure. It allows to continually develop in a form that seems blocking, while handling both success and failure in a comfortable, sane manner. I'm sorry I was not aware of it when I gave my "Asynchronous Programming" talk in YAPC::EU 2012.

Fortunately for us, we are lucky to have such skilled, cool, and handsome people as Stevan Little and Paul Evans, to implement Promises and Future, respectively, for us in Perl. Nowadays for my more complicated projects, I use Promises with joy. Stevan and Paul, each in their developed modules, have provided numerous examples (and introductory text) to how this pattern works and how to use it in various situations. Also, they are (as always) more than helpful when asked for pointers.

Letting go of my crush of Stevan and Paul for a bit, I'd like to explain a specific issue that troubled me in a project, which lead me to write recursive promises. What is it, why did I need it and how did I accomplish it? I'll be using the Promises module, though the principle is exactly the same as in the Future module.

I have a project which deploys a new server. This requires raising a ticket to create an instance, monitoring the status, and then running commands on it sequentially. First command updates, the next installs some prereqs, the one after that installs the deployment infrastructure and the last one is run locally to deploy our code to the server. This clearly leads to the arrow-head format:

run_remote_command $host, 'aptitude update && aptitude safe-upgrade', sub { # check possible fail or succeed run_remote_command $host, 'aptitude install build-essential curl...', sub { # check possible fail or succeed run_remote_command $host, 'gem install chef...', sub { # check possible fail or succeed run_local_command 'chef solo cook ...', sub { # check possible fail or succeed } } } };

Not so pretty, is it? Well, I can use promises to make it more manageable:

use Promises 'deferred'; my $def = deferred; run_remote_command $host, 'aptitude update && aptitude safe-upgrade', sub { if ( my $result = shift ) { $def->resolve($result); } else { $def->reject("Failed update/safe-upgrade"); } }; $def->promise->then( sub { my $def = deferred;

run_remote_command $host, 'aptitude install build-essential curl...', sub { if ( my $result = shift ) {

$def->resolve($result);

} else {

$def->reject("Failed update/safe-upgrade");

}

} );

return $def->promise;

}, sub {

my $error = shift;

die $error;

} )->then( sub {

my $def = deferred;

run_remote_command $host, 'gem install chef...', sub {

if ( my $result = shift ) {

$def->resolve($result);

} else {

$def->reject("Failed installing chef");

}

} );

return $def->promise;

}, sub {

my $error = shift;

die $error;

} );

...



You get the idea, right? Create a deferred promise, resolve or reject it later, and pass back the promise itself. Use the promise returned to chain more promises with deferred promises that resolve or reject later. Very nice. If you have a hard time following this, try this cookbook.

My problem was having to hardcode all the commands and have a known list of commands in advance, in order to write a promise step for each. You cannot have an arbitrary number of promises steps. At least not too easily. I wanted an array of any number of commands that will be run separately in each promise step. My solution was a recursive deferred promises pattern (at least that's how I'm calling it for now - trademarked!), illustrated in this Github gist:



As you can see, I set up the first promise and chain it to a code reference that calls itself recursively until the array of actions is exhausted. A good point made by Nicholas Perez was that instead of defining a variable for the callback and only then defining it as a coderef (thus allowing it to call itself), I could have used the new __SUB__ option more modern Perls (since Perl 5.16) have. In production my code has to be more backwards compatible, so I had to use my method instead.

Epilogue, sort of:

The one thing I want you to take from this post is not that I'm cool (although I am, very much so - you should definitely consider being my friend), but rather that when we talk about creating cool stuff (re: John @genehack Anderson's talk on this), we talk about stuff like Future and Promises. That's an example of writing something awesome, even if we didn't invent the concept. If you look at the code of Promises, for example, it's not even complicated or hairy. It's short, clean and elegant, and it provides a great service for programmers, and a wonderful standing point when we want to promote Perl as a strong and modern language. "Futures/Promises? Oh yeah, we got that!"