Categories
JavaScript JavaScript Basics

Why it’s time to use the async-await in JavaScript?

Spread the love

Asynchronous code is a regular part of JavaScript. As web apps get more complex, there’ll be more need for asynchronous code since JavaScript is single-threaded, and asynchronous code prevents the blocking of the main thread.

In this article, we’ll look at why async and await is the way to go for writing asynchronous code.

The Old Way to Write Promise Code

Before we look at async and await, we have to look at what it’s like in the old days.

In the olden days, we chain promises by writing something like the following:

Promise.resolve(1)  
  .then(val => Promise.resolve(2))  
  .then(val => Promise.resolve(3))  
  .then(val => Promise.resolve(4))  
  .then(val => Promise.resolve(5))  
  .then(val => Promise.resolve(6))

As we can see, we have to call then a lot and in each then call, we have to pass in a callback function to return the next promise. This is a pain because it’s very long-winded. If we want to do something like gathering the resolved values from each promise, then the code gets even longer.

For example, to gather the resolved values into an array, we have to write something like:

let vals = [];  
Promise.resolve(1)  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(2)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(3)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(4)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(5)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(6)  
  })  
  .then(val => {  
    vals.push(val);  
    console.log(vals)  
  });

Then we get [1, 2, 3, 4, 5, 6] as the value of vals.

As we can see, the number of lines of code exploded compared to the first example.

There’s lots of repetition and takes up lots of space in a file.

To deal with this, it’s time to move to the present with the async-await syntax for chaining promises.

Async and Await

To clean up the code in the example above, we can use async and await to do this.

We start a function declaration with async and we use the await keyword inside. await does the same thing as the then callbacks. It takes the resolved value of the promise and lets us do stuff with it.

With async and await, we can turn:

let vals = [];  
Promise.resolve(1)  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(2)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(3)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(4)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(5)  
  })  
  .then(val => {  
    vals.push(val);  
    return Promise.resolve(6)  
  })  
  .then(val => {  
    vals.push(val);  
    console.log(vals)  
  });

into:

(async () => {  
  const val = await Promise.resolve(1);  
  const val2 = await Promise.resolve(2);  
  const val3 = await Promise.resolve(3);  
  const val4 = await Promise.resolve(4);  
  const val5 = await Promise.resolve(5);  
  const val6 = await Promise.resolve(6);  
  const vals = [val, val2, val3, val4, val5, val6];  
  console.log(vals);  
})();

The await in the code above indicates that the code to the right of it returns a promise, and it’ll wait for the promise to be fulfilled until it moves on to the next one. Also, the resolved value can be assigned with the = and the variable or constant to the left of it.

The values can be gathered into an array at the end of the function and we can log it.

Even though this looks like synchronous code, async function can only return promises, so if we return vals, we’ll get a promise that resolves to the value of vals.

This means that:

const getVals = () => {  
  let vals = [];  
  Promise.resolve(1)  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(2)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(3)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(4)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(5)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(6)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(vals);  
    });  
}

is the same as:

const getValsAsyncAwait = async () => {  
  const val = await Promise.resolve(1);  
  const val2 = await Promise.resolve(2);  
  const val3 = await Promise.resolve(3);  
  const val4 = await Promise.resolve(4);  
  const val5 = await Promise.resolve(5);  
  const val6 = await Promise.resolve(6);  
  const vals = [val, val2, val3, val4, val5, val6];  
  return vals;  
};

We can use await on both and see what we get.

This block:

const getVals = () => {  
  let vals = [];  
  return Promise.resolve(1)  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(2)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(3)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(4)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(5)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(6)  
    })  
    .then(val => {  
      vals.push(val);  
      return Promise.resolve(vals);  
    });  
}

(async () => {  
  const values = await getVals();  
  console.log(values);  
})()

gets us [1, 2, 3, 4, 5, 6] from the console.log.

And:

const getValsAsyncAwait = async () => {  
  const val = await Promise.resolve(1);  
  const val2 = await Promise.resolve(2);  
  const val3 = await Promise.resolve(3);  
  const val4 = await Promise.resolve(4);  
  const val5 = await Promise.resolve(5);  
  const val6 = await Promise.resolve(6);  
  const vals = [val, val2, val3, val4, val5, val6];  
  return vals;  
};

(async () => {  
  const values = await getValsAsyncAwait();  
  console.log(values);  
})()

also gets us [1, 2, 3, 4, 5, 6] from the console.log. So they do exactly the same thing, just with much fewer lines of code.

Catching Errors

In the olden days we catch errors with the catch and the callback passed into it.

For example, we write something like:

Promise.resolve(1)  
  .then(val => console.log(val))  
  .catch(err => console.log(err));

Now we can use try...catch with async and await:

(async () => {  
  try {  
    const val = await Promise.resolve(1);  
  } catch (err) {  
    console.log(err);  
  }  
})();

This doesn’t save much space, but this may still come in handy in case of errors.

As we can see, async and await make code for chaining promises so much shorter. It’s been available since 2017, so it’s supported by most modern browsers. The time to use this to clean up our code is definitely now.

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 *