Node.js Async: How to Use ES6 Promises
Introduction
ES6 promises are harder to learn then traditional callback interfaces.
But after you get used to them makes your code much more readable and easier to develop.
Basics
A promise is just an object.
A promise can be either pending or settled.
A settled promise can be fulfilled or rejected.
We can ‘listen’ for a certain promise to be settled with the .then() and .catch().
With a single then() you can receive two callback functions.
One for onfulfilled success and one for onfailure.
1 | function myFunction() { |
Then
A then can only placed after promise.
Thens can be chained.
The return value of the previous then will be the argument for the next then in the chain.
If that return value is a promise the next then in the chain only gets called if that promise is fulfilled.
If the then returns nothing the next then argument will be undefined.
Things you can return from a then:
- You can simply return a value from a then.
- You can return a promise.
- You can return a function that returns a promise.
- If you use return synchronously it does not get called again.
Its important to note that in a promise chain if you have an async function call which returns a promise you must return it inside the then() otherwise the chain goes on without waiting for the promise.
1 | function myAsync(val) { |
Catch in chains
You can throw synch error.
You can reject a promise.
You can directly use Promise.resolve() and Promise.reject().
You can listen to error from a previous then with a second argument from then.
You can listen to a whole chain with catch.
You can add a then after a catch.
1 | function myAsyncReject() { |
Branching chains
You can place a then inside a then or event in a catch as long it has an async call before it.
A child branch can have its own thens and catches.
Returning from the last then in a branch returns the parents branch next then.
Throwing an error in a child branch triggers the child branch catch.
If it does not have a catch it lands in the parents nearest catch and so on.
If you return from a child 2 levels deep and there are no immidiate parent then it will return the value for the nearest parent then aka. first parent then.
Here we have multiple .then branches.
At the end of the first branch we throw an error to the parent branch catch.
The second .then branch returns a value to the parent branch.
1 | const fs = require('fs'); |
Dealing with callback interface
All of the official node.js modules and most of the npm packages use the callback interface.
This can make it tricky to use them with promises.
Placing directly inside a then
This is ideal if you need a callback based interface at the end of the chain.
The problem with this is that if you place another then after the callback it will fire before the actual async functions has been finished.
1 | Promise.resolve() |
Wrapping in a promise
By wrapping in a promise a callback function can be used in a promise chain easily.
I wrap most of my functions in a similar way.
1 | Promise.resolve() |
Promisify
You can promisfy an existing callback based module with Promisify. This way you can promisify whole modules without having to modify it.
1 | var promisify = require("promisify-node"); |
Promise.all
Good for waiting for all the promises to fulfill at once.
1 | var p1 = new Promise((resolve, reject) => { |
Promise.race
Waiting for multiple promises but only get the one which resolves faster.
1 | var p1 = new Promise(function(resolve, reject) { |
Iterating over arrays with promises
This can be tricky. You can use custom Promise libraries to make it easier but you can check out my tutorial on Sequential iteration pattern for an easy solution that works most of the time.