Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Async Code

Spread the love

JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.

In this article, we’ll look at the best practices for writing asynchronous code with Javascript.

Use Promises

Promises are very useful for writing asynchronous code in JavaScript since we can chain multiple pieces of asynchronous code together without nesting multiple asynchronous callbacks.

We can’t nest too many asynchronous callbacks deeply because it’s very hard to read and follow. Therefore, it’s very easy to create bugs and making changes will be very slow.

With promises, we can chain multiple promises together and make writing asynchronous code in a series very easy.

For instance, we can write the following code to chain multiple promises together as follows:

Promise.resolve(1)
  .then(val => Promise.resolve(val * 2))
  .then(val => Promise.resolve(val * 3))

In the code above, we called Promise.resolve , which is asynchronous and returns a promise. Each Promise.resolve call is queued at the end of the event loop and runs when the JavaScript main execution thread is free of any queued tasks.

The chaining we did above is much cleaner than we would have with nested callbacks. The val just takes the value we have from the promise that’s resolved before.

As we can see, we can also take the value from the previously resolved promise and do something with it. This is something that’s hard to do with nested callbacks as we have to do 3 levels of nesting to do the same thing.

To catch errors that occurs when any promise is being executed, we can call catch with a callback as follows:

Promise.resolve(1)
  .then(val => Promise.resolve(val * 2))
  .then(val => Promise.reject('fail'))
  .catch(err => console.log(err))

In the code above, we called Promise.reject , which will cause the promise chain to fail. Promise.reject takes a reason for the failure and the reason will be available in the err object in the catch callback.

So, we’ll see 'fail' logged from the promise callback.

The promise stops running when the first promise that fails is run, so we can only have one catch call in the promise chain.

The error handling is also something that’s not available with nested async callbacks unless the code that takes the callback explicitly returns an error as a parameter in the callback.

Also, we have to do more nesting to handle errors in nested async callbacks, which means that it makes reading the code even harder and create more bugs.

To make promises even shorter, we can use the async and await syntax for chaining promises.

For instance, we can write the following code to use async and await so that we can remove the then and callbacks but do the same thing as before:

(async () => {
  const val1 = await Promise.resolve(1);
  const val2 = await Promise.resolve(val1 * 2);
  const val3 = await Promise.resolve(val2 * 3);
})()

This is cleaner than using then and callbacks since we now have no callbacks in our code and it’s only 3 lines. Also, the readability of the code isn’t made worse by using it.

val1 , val2 , and val3 have the same values the parameter in the then callbacks, which are the resolved values.

To catch errors when a promise in the promise chain fails, we can just use try...catch as we do with synchronous code.

For instance, we can write the following code to catch promises with async and await :

(async () => {
  try {
    const val1 = await Promise.resolve(1);
    const val2 = await Promise.resolve(val1 * 2);
    await Promise.reject('fail')
  } catch (err) {
    console.log(err)
  }
})()

In the code above, we wrapped a try...catch block around our promise code so that we can catch errors with our promise chain. It stops running when the promise fails so one catch block will do everything.

Photo by Hello I’m Nik ? on Unsplash

Run Unrelated Promises in Parallel with Promise.all

We should use Promise.all to run promises that are unrelated in parallel. This way, we won’t have to wait for one promise to resolve before running another unnecessarily, thereby speeding up our code.

Promise.all takes an array of promise and returns a promise that has the array of resolved values.

For instance, we can write the following code to use Promise.all :

Promise.all([
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3),
  ])
  .then(([val1, val2, val3]) => {
    console.log(val1, val2, val3);
  })

In the code above, we have 3 promises with Promise.resolve in an array. We then use that to call Promise.all to resolve them in parallel. Then we get the resolved values in the callback and log them.

With the async and await syntax, we can write:

(async () => {
  const [val1, val2, val3] = await Promise.all([
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3),
  ])
  console.log(val1, val2, val3);
})();

since Promise.all returns a promise.

Conclusion

When writing async code in JavaScript, we should be using promises. This way, they can be chained easily and we can also run them in parallel with Promise.all .

Removing nested increases readability by a lot and so mistakes are less likely to be made. Also, error handling is standard since we can call catch with promises to catch errors.

The async and await syntax takes chaining promises shorter. Also, we can catch errors with it with try...catch .

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *