I’ve been spending a lot of time these days in Node.js working with Promises. Our team has adopted them as our pattern for writing clean & comprehensible asynchronous code.
Personally, I find code crafted in Promise style to be much more legible and easy to follow than the callback-based equivalent. Those of us who’ve drank the kool-aid can become kinda passionate about it.
I’ll be presenting some flow patterns below which I keep coming back to – for better or for worse – to illustrate the kind of well-intentioned wackiness you may may encounter in Promise-influence projects. I would add that, to date, these patterns have survived the PR review scrutiny of my peers on multiple occasions. Also, they are heavily influenced by the rich feature-set of the
Yes, Promises do introduce a performance hit into your codebase. I don’t have concrete metrics here to quote to you, but our team has decided that it is an acceptable loss considering the benefits that Promises offer. A few of the more esoteric benefits are:
arguments.length-based optional-arg and vararg call signatures become easier to implement cleanly without the trailing callback Function.
returnsuddenly starts meaning something again in asynchronous Functions, rather than merely providing control flow.
- Your code stops releasing Zalgo all the time – provided that your framework of choice is built with restraining Zalgo in mind (as
bluebirdis). Also, Z̪̝̞̙̞̾a͕̼̹̣̬ͧ̔ͧ͆̌l̜̳͓͒g̦̔͂ͬͫ̓͊͗ȍ̫͊̉.
How Many LOCs Do You Want?
Which of the following styles do you find more legible? I’ve gotten into the habit of writing the latter, even though it inflates your line count and visual whitespace. Frankly, I like whitespace.
It even makes the typical first-line indentation quirkiness read pretty well. Especially with a large argument count.
Throw Or Reject
I’ve found it’s good rule to stick with either a
Promise.reject coding style, rather than hopping back & forth between them.
Of course, rules are are meant to be broken; don’t compromise your code’s legibility just to accomodate a particular style.
If you’re going to be taking the
throw route, it’s best to only do so once you’re wrapped within a Promise. Otherwise, your caller might get a nasty surprise – as they would if your Function suddenly returned
null or something equally non-Promise-y.
Ensuring that Errors stay inside the Promise chain allows for much more graceful handling and fewer process.on(‘uncaughtException’)s. This is another place where the overhead of constructing a Promise wrapper to gain code stability is well worth the performance hit.
Deferred vs. new Promise
bluebird library provides a very simple convenience method to create a ‘deferred’ Promise utility Object.
I find code that uses a ‘deferred’ Object to read better than code that uses the Promise constructor equivalent. I feel the same way when writing un-contrived code which actually needs to respect Error conditions.
Finally … here’s a lovely little pattern – or anti-pattern? – made possible by Object identity comparison.
The objective is to short-circuit the Promise execution chain. Early return methods often go hand-in-hand with carrying something forward from earlier in the Promise chain, hence the
There’s obtuseness sneaking in here, even with a simplified example using informative variable names. Admittedly, an early return Function is easier to write as pure CPS, or using an async support library. It’s also possible to omit the
EARLY_RETURN by using Promise sub-chains, but you can end up with indentation hell all over again.
I’d Say That’s Plenty
No more. I promise.