DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Originally published at thewebdev.info

JavaScript Best Practices — Promises

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

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');
})()
Enter fullscreen mode Exit fullscreen mode

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) => {
    //...
  })
Enter fullscreen mode Exit fullscreen mode

We can clean this up with async and await as follows:

(async () => {
  const val1 = await promise1;
  //...
  const val2 = await promise2;
  //...
  const val3 = await promise3;
  //...
})();
Enter fullscreen mode Exit fullscreen mode

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) => {
    //...
  })
Enter fullscreen mode Exit fullscreen mode

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) {
    //...
  }
})();
Enter fullscreen mode Exit fullscreen mode

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) => {
    //...
  })
Enter fullscreen mode Exit fullscreen mode

Or we can write:

(async () => {
  try {
    const val1 = await promise1;
    //...
    const val2 = await promise2;
    //...
    const val3 = await promise3;
    //...
  } catch (ex) {
    console.log(ex);
  } finally {
    //...
  }
})();
Enter fullscreen mode Exit fullscreen mode

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) => {
    //...
  })
Enter fullscreen mode Exit fullscreen mode

If we use async and await, we can write:

(async () => {
  const [val1, val2, val3] = await Promise.all([
    promise1,
    promise2,
    promise3,
  ])
})();
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)