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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myFunction() {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve('async resolve');
reject('async reject');
}, 1000);
});
}

myFunction()
.then( val => {
console.log(val);
}, err => {
console.log(err);
});

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function myAsync(val) {
return new Promise((resolve, reject) => {
resolve(val+1)
});
}

Promise.resolve(1) // 1
.then( value => {
return new Promise ( (resolve, reject) => {
resolve(value+1); // 2
});
})
.then( value => {
return myAsync(value); // 3
})
.then( value => {
return (value+1); // 4
})
.then( value => {
console.log(value);
})
.catch( error => {
console.log(error);
});

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function myAsyncReject() {
return new Promise((resolve, reject) => {
reject('Rejected from async');
});
}

Promise.resolve(1)
.then( value => {
throw 'sync throw'; // Synch throw
})
.then( value => {
// Not getting called
}, (err) => {
console.log(err);
return 'Recovering from sync throw';
})
.then( recovery => {
console.log(recovery);
})
.then( () => { // Since nothing is returned in the previous then
return myAsyncReject(); // Async Reject
})
.then( val => { // Should not be called
console.log('Should not be called');
})
.catch( error => {
console.log(error);
});

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const fs = require('fs');

function myAsync(val) {
return new Promise((resolve, reject) => {
resolve(val+1)
});
}

function myAsync2(callback){
return new Promise((resolve, reject) => {
fs.readFile('./test.txt', 'utf-8', (err, data) => {
callback(data, val =>{
resolve(val);
});
});
});
}

function myCallback(data, cb){
setTimeout(function() {
cb(data + "_changed");
}, 1000);
}

Promise.resolve(1) // 1
.then( value => {
return new Promise ( (resolve, reject) => {
resolve(value+1); // 2
});
})
.then( value => {
return myAsync(value); // 3
})
.then( value => {
return (value+1); // 4
})
.then( value => {
console.log(value);
})
.then( () => {
return myAsync(0) // 1
.then(val => {
return (val+1); // 2
})
.then(val => { // 3
return new Promise( (resolve, reject) => {
resolve(val+1)
})
.then(val => { // 4
return val+1;
});
})
.then(val => {
console.log(val);
throw('inside error'); // Inner catch
})
.catch(err => {
console.log(err);
throw('outside error'); // Outside catch
});
})
.catch( error => {
console.log(error);
return myAsync2(myCallback)
.then(val => {
console.log(val);
return('hi');
})
})
.then( greeting => {
console.log(greeting); // hi
})

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
2
3
4
5
6
Promise.resolve()
.then(() => {
setTimeout(() => {
console.log('hi');
}, 1000);
});

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
2
3
4
5
6
7
8
9
10
11
Promise.resolve()
.then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi');
}, 1000);
});
})
.then(greeting => {
console.log(greeting);
})

Promisify

You can promisfy an existing callback based module with Promisify. This way you can promisify whole modules without having to modify it.

1
2
3
4
5
6
7
8
var promisify = require("promisify-node");
var fs = promisify("fs");

// This function has been identified as an asynchronous function so it has
// been automatically wrapped.
fs.readFile("/etc/passwd").then(function(contents) {
console.log(contents);
});

Promise.all

Good for waiting for all the promises to fulfill at once.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var p1 = new Promise((resolve, reject) => { 
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 4000, 'four');
});

Promise.all([p1, p2, p3, p4]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});

Promise.race

Waiting for multiple promises but only get the one which resolves faster.

1
2
3
4
5
6
7
8
9
10
11
var p1 = new Promise(function(resolve, reject) { 
setTimeout(resolve, 500, 'one');
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});

Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// Both resolve, but p2 is faster
});

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.