---
title: 'JavaScript Promise and Async/Await'
publishedAt: '2022-04-20'
summary: 'A short visual guide on what a Promise is in JavaScript, and how to avoid call back hell.'
---

## 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:

- <em>pending</em>: initial state, neither fulfilled nor rejected.
- <em>fulfilled</em>: meaning that the operation was completed successfully.
- <em>rejected</em>: meaning that the operation failed.

## Visualization of a Promise

![image](/static/blog/promises.webp)

## 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

```js
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:

```js
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:

```js
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:

```js
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:

```js
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:

```js
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:

```js
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:

```js
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.
