In node I/O bound operations — such as networking, databases, or file systems — are non-blocking or asynchronous (for good reason). This kind of programming naturally lends itself to callback driven APIs which in turn can lead to unwieldy nested callback structures. Which has affectionally been referred to as the “Pyramid of Doom” in this article.
Promise-based APIs provide an alternative to callback-based APIs. More importantly, they solve the problem that callback APIs introduce: the readability and intent of asynchronous code.
What are promises?
The idea behind Promise-based APIs is that a function will return a promise for an object in the future. Promises can be chained together and as each promise is fulfilled the next promise in line is executed. If a promise can’t be fulfilled it raises an error which can be handled, otherwise the chain of promises stops execution.
So instead of:
We’d do something like this:
We went with a library named Q which provides the kind of functionality I demonstrated above. It is a fairly flexible library that has built in support for node style callbacks (function with error and result). However, we noticed the the third party node libraries may have a different set of callback conventions which required us to wrap them.
Q provides a function named
ncall which takes a function, an object to execute that function on, and arguments. Q assumes that the first argument of the callback is an error and the second argument is the result. If the error is non-null it assumes an error occurred and raises a failure. Otherwise it fulfills the promise with the given result.
Wrapping non-compliant APIs
In the contrived example below we wrap a library that doesn’t use node-style callbacks. We use
Q.defer() to return a promise for a result in the future and wrap the
We also wrote this helper to automatically “qify” objects where it wraps methods on an existing object into “Q” style methods.
Alternative to promises
node-fibers is a library that provides a ‘blocking style’ way of executing asynchronous code. It doesn’t actually block (that would be bad), instead it uses continuations to defer the execution of code until an asynchronous operation has completed.