I still remember the day when I discovered event-driving programming – what I felt then one might compare to the discovery that my bike will not always have three wheels. What is more, quite soon it turned out that the misuse of the new standard will most likely prevent anyone else from going through my code whatsoever.
2. What is a “callback hell” then?
Can you see this outline of the pyramid lurking in the background?
It turns out to be a problem when the level of code nesting significantly trammels reading of the code (even after quite a few cups of coffee!) and error causes problems with the application…
Promises (or “
thennables”, as we interact with it by passing callback to its
then function ) is a simple concept that makes life easier – they are an object that represents a value which will eventually be available. They are used for asynchronous or deferred computations and can be an alternative to the usual callback approach.
At their most basic, promises are a bit like event listeners except:
- A promise can only succeed or fail once (and it cannot switch from success to failure or vice versa).
- If a promise has succeeded or failed and you later add a success/failure callback, the correct callback will be called, even though the event took place earlier.
This is very useful for async success/failure, because you do not have to be interested in the time something became available, and more interested in responding to the outcome.
- Flattened callbacks
- Return values from asynchronous function
- Throw and Catch exceptions
When using promises we take callbacks – especially nested callbacks where you want to perform a series of actions, each one after the resolution of the former- and “flatten out” the code required to do this.
Compare this promise example…
…to a basic callback
And this is a promisified version of our callback hell example:
Promises can be used both on the browser (you can check the support here) or in Node.js apps.
You can probably see the benefits of using promises already. Most importantly, promises are amazing because most promise libraries (eg. Bluebird and Q) come with the promisify function that help you convert all your node style functions into promise returning functions.
However, there are also some disadvantages you should know of when it comes to promises:
- You can’t cancel a Promise. Once you have a promise, the process that produces that promise’s resolution is already under way!*
- Promises are fulfilled (resolved or rejected) with a single value. A promise conceptually represents a value over time so while you can represent composite values you can’t put multiple values in a promise.
Promises are great for asynchronous functions that call their callback once. But promise libraries prevent you from ‘over promising’. As I have mentioned before there are some promise libraries that help you convert all your node style functions into promise returning functions. I will focus on Bluebird now as it has the incredibly useful functionality of enabling you to ‘promisfy’ modules which do not return promises. For example, to promisfy the fs module, simply require bluebird and a promisified version of fs.
Because of the fact that you can promisfy modules it is unlikely that you will have to create promises yourself very often. It is, however, still useful to know how. Creating a new promise provides you with the resolve and reject callback. Pass into each of these the appropriate information.
6. ES 7 (async/await)
Our code looks much better now, we have now a simple linear event flow – could it be better?
Apparently yes! There is still a lot of extra syntax we do not need and with ES 7
if/else? 🙂 I thought so – so what are you going to say about
try/catch? There is no need to write libraries to make simple logical operations.
Back to promises, not only are they the basis of async/await, but you’ll still need to resort to them when implementing some more complex async IO scenarios (eg. running multiple operations in parallel). In general, await keyword takes a promise, waits for it’s value to be available, and then returns that value. The keyword async, in turn, makes the function always return a promise, and allows using await in its body.
If you think that using async and await gives you control over concurrent programming then try the
Promise.all( ) method – it is even more powerful!
Let’s assume you want to perform two asynchronous computations in parallel –
asyncFunc1( ) and
asyncFunc2( ) :
Both functions –
asyncFunc1( ) and
asyncFunc2( ) are used without using
then( ) chaining. It means neither more nor less, that they are both executed immediately. We have created two separate “threads” and once they are finished (with a result or an error) the execution is joined into a single “thread” (
handleSuccess( ) or
As you can see that’s quite a lot of code for a such simple operation! This approach involves way too much manual work which often results in generating errors.
Promise.all( ) is a built-in method. It takes an iretable argument –
iretable such as an
String and When all the promises of the iretable argument have resolved (or if the argument contains no promises) it returns a single
Here is our previous example but this time we are using
Promise.all( ) method:
We just wrote very simple yet powerful and easy to understand asynchronous code! Well done!
To sum up:
- is a new way to write asynchronous code – earlier ways for dealing with asynchronous code are callbacks and promises.
- it is (just like promises) non blocking.
- is actually built on top of promises. It cannot be used with plain callbacks or node callbacks.
- makes our code look and behave a little bit more like synchronous code – this makes it so powerful.