Some ways of writing JavaScript code are better than others.
In this article, we’ll look at best practices for using the latest JavaScript promise features.
Converting Non-Promise Async Code to Promises
We can convert the non-promise async code to promises with the util.promisify()
method. This lets us convert many functions with async callbacks with the signature of the form (err, res)
, where err
is an object with errors and res
has the results of the async code.
Any function that has a promising form defined for it can also use this utility to return the promise form. As long as the promise version is stored as the value of the symbol property util.promisify.custom
, we can get the promise version.
For instance, since setTimeout
has a promise-based version in the standard library of Node.js, we can convert it to a promise as follows:
const util = require('util');
const sleep = util.promisify(setTimeout);
(async () => {
await sleep(1000);
console.log('slept');
})()
Now we have a sleep
function that pauses execution for the time we want, instead of having to nest callbacks.
Asynchronous Chaining
In the old way, we chain promises with the then
method.
For instance, we write:
promise1
.then((res) => {
//...
return promise2
})
.then((res) => {
//...
return promise3
})
.then((res) => {
//...
})
We can clean this up with async
and await
as follows:
(async () => {
const val1 = await promise1;
//...
const val2 = await promise2;
//...
const val3 = await promise3;
//...
})();
This is much cleaner as the resolve promise values are assigned to the constants or variables on the left side.
Using Catch to Catch Errors
As with synchronous code, we have to catch errors and handle them gracefully.
To do that with promise code, we can call the catch
method. For instance:
promise1
.then((res) => {
//...
return promise2
})
.then((res) => {
//...
return promise3
})
.then((res) => {
//...
})
.catch((err) => {
//...
})
If we use async
and await
, then we use try...catch
as we do with synchronous code, as follows:
(async () => {
try {
const val1 = await promise1;
//...
const val2 = await promise2;
//...
const val3 = await promise3;
//...
} catch (ex) {
//...
}
})();
The code in catch
in both examples would run when the first promise is rejected.
Use Finally to Run Code That Should Run Regardless of the Outcome of the Promises
If we need to run code that should be run regardless of the outcomes of the promise chain, then we should use the finally
method or the finally
block for async and await code.
For instance, we can run clean up code in finally
as follows:
promise1
.then((res) => {
//...
return promise2
})
.then((res) => {
//...
return promise3
})
.then((res) => {
//...
})
.finally((err) => {
//...
})
Or we can write:
(async () => {
try {
const val1 = await promise1;
//...
const val2 = await promise2;
//...
const val3 = await promise3;
//...
} catch (ex) {
console.log(ex);
} finally {
//...
}
})();
Multiple Asynchronous Calls with Promise.all()
If we want to run multiple unrelated promises at once, then we should use Promise.all
.
For instance, we can run them all at once as follows:
Promise.all([
promise1,
promise2,
promise3,
])
.then((res) => {
//...
})
If we use async
and await
, we can write:
(async () => {
const [val1, val2, val3] = await Promise.all([
promise1,
promise2,
promise3,
])
})();
In both examples, the resolved values of all three promises are either in res
or assigned to an array on the left (in the second example).
This way, they run all at once instead of waiting for each to resolve.
Summary
- We can clean up our async code by using promises.
- Converting to async code to promises can be done with Node apps by using the
util.promisify
method. - If we want to chain promises, before sure to add the
catch
method or block to handle errors gracefully. finally
method or block can be used to run code that’s always run regardless of the promises outcomes.Promise.all
is great for running unrelated promises all at once.