JavaScript Promise and Async/Await

Published on
Dale Larroder-
4 min read

Overview

What is a Promise?

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

A Promise is in one of these states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.

Visualization of a Promise

image

What is Async/Await?

Async/Await was the result of callback hell and nested promises from the old era of JavaScript

Visualization of callback hell

const doSomeFetch = fetch('/some-api')

doSomeFetch()
  .then((data) => doAnotherFetch(data))
  .then((result) => doAnoterFetch(result))
  .then((outcome) => probablyLastFetch(outcome))

But in the real world, it would look some something like this:

doSomeFetch().then((data) => {
  doAnotherFetch(data)
    .then((result) => {
      doAnoterFetch(data, result)
        .then((outcome) => {
          probablyLastFetch(data, result, outcome)
            .then((fin) => {
              // Ah Finally!
            })
            .catch((e) => {
              handleError(e)
            })
        })
        .catch((e) => {
          handleError(e)
        })
    })
    .catch((e) => {
      handleError(e)
    })
})

Async/Await allows us to write code like this:

const data = await doSomeFetch()
const result = await doAnotherFetch(data)
const outcome = await doAnoterFetch(data, result)
const fin = await probablyLastFetch(data, result, outcome)

Looks cool right? But how about error handling? It would look like this:

let data, result, outcome, fin

try {
  data = await doSomeFetch()
} catch (e) {
  handleError(e)
}

try {
  result = await doAnotherFetch(data)
} catch (e) {
  handleError(e)
}

try {
  outcome = await doAnoterFetch(data, result)
} catch (e) {
  handleError(e)
}

try {
  fin = await probablyLastFetch(data, result, outcome)
} catch (e) {
  handleError(e)
}

You can also use a single try catch for all of the promises if you don't care about catching the individual errors of each promises.

This looks better:

try {
  const data = await doSomeFetch()
  const result = await doAnotherFetch(data)
  const outcome = await doAnoterFetch(data, result)
  const fin = await probablyLastFetch(data, result, outcome)
} catch (e) {
  handleError(e)
}

But it you want able to catch and identify which block threw an error and handle each case differently, we append a catch block to each async method, kind of similar to the promises way:

const data = await doSomeFetch().catch(handleError)
const result = await doAnotherFetch(data).catch(handleError)
const outcome = await doAnoterFetch(data, result).catch(handleError)
const fin = await probablyLastFetch(data, result, outcome).catch(handleError)

A Clean Approach

Building up on our previous examples, let's create a better error handler:

export async function fancyFetch(fetcher, ...args) {
  try {
    const data = await fetcher(...args)
    return [data, null]
  } catch (error) {
    log(error)
    return [null, error]
  }
}

If we have data, we return an array with data as the first parameter and error as null, if we get and error, we return data as null and the error object.

Now to re-write up the async/await example using an array destructring syntax:

const [data, dataError] = await fancyFetch(doSomeFetch);
if(dataError) { // got the error context, do something! }

const [result, resultError] = await fancyFetch(doAnotherFetch, data);
if(resultError) { // got the error context, do something! }

const [outcome, outcomeError] = await fancyFetch(doAnotherFetch, data, result);
if(outcomeError) { // got the error context, do something! }

const [fin, finError] = await fancyFetch(probablyLastFetch, data, result, outcome);
if(finError) { // got the error context, do something! }

Conclusion

  • If you don't care about handling errors for individual async calls, go with a single try/catch.
  • If you care about handling errors for individual async calls, but do not care about the things inside the context of the catch block, use await doSomeFetch().catch(handleError);
  • If the answer for both of the above points were 'No', then the fancyFetch() approach should work for you.