Comparing Callbacks, Promises and Async Await in TypeScript
Tags: webdev, typescript, vscode, beginners
Published: 2020-02-23 17:40:41

---
title: Comparing Callbacks, Promises and Async Await in TypeScript
published: true
description: How do callbacks, promises and async/await compare to each other? This article shows the same scenario using each of these three techniques so you can see the differences and choose which appeals most to you.
tags: webdev, typescript, vscode, beginners
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/lr0xwu98wifgdlksgpxr.png
canonical_url: https://johnpapa.net/async-comparisons/
---

How do callbacks, promises and async/await compare to each other? This article shows the same scenario using each of these three techniques so you can see the differences and choose which appeals most to you.

The code uses [TypeScript](http://www.typescriptlang.org/?wt.mc_id=devto-blog-jopapa), but can easily be adapted to JavaScript.

> Learn more about this code in my course [Creating Asynchronous TypeScript Code](https://pluralsight.pxf.io/V9eR6) on Pluralsight.

This article shows three different techniques to get an object graph of a hero with that hero's orders and account rep.

The scenario for these examples are that there is a set of heroes. Each hero has to shop, so they make orders. And each hero has a dedicated account rep for their orders. Heroes are like customers, if that helps 😄

## Callbacks

When writing callbacks we end up with a series of nested calls. This is easy to see when we look a the code below as it all tends to drift to the right. This drifting is also known as the "Pyramid of Doom". 

The code below gets a hero by the hero's email. Then it gets the orders for the hero and merges them into the hero object. Then it gets the account repo for the hero and merges that data into the hero object. Finally, it returns the hero with all of the orders and account rep data.

```typescript
const getHeroTreeCallback = function(
  email: string,
  callback: Callback<Hero>,
  callbackError?: CallbackError,
) {
  getHeroCallback(
    email,
    hero => {
      getOrdersCallback(
        hero.id,
        orders => {
          hero.orders = orders;
          getAccountRepCallback(
            hero.id,
            accountRep => {
              hero.accountRep = accountRep;
              callback(hero);
            },
            error => callbackError(error),
          );
        },
        error => callbackError(error),
      );
    },
    error => callbackError(error),
  );
};
```

### Individual Callbacks

The `getHeroTeeCallback` function calls nested functions. You can see these in the following code example.

There are three functions here. Each gets the Hero, the Hero's orders, and the Hero's account reps, respectively. Notice that each follows a pattern of using axios to get the data over http, and invokes the `callback` or `callbackError` function based on whether the code worked or encountered an error.

```typescript
const getHeroCallback = function(
  email: string,
  callback: Callback<Hero>,
  callbackError?: CallbackError,
) {
  axios
    .get<Hero[]>(`${apiUrl}/heroes?email=${email}`)
    .then((response: AxiosResponse<Hero[]>) => {
      const data = parseList<Hero>(response);
      const hero = data[0];
      callback(hero);
    })
    .catch((error: AxiosError) => {
      console.error(`Developer Error: Async Data Error: ${error.message}`);
      callbackError(`Oh no! We're unable to fetch the Hero`);
    });
};

const getOrdersCallback = function(
  heroId: number,
  callback: Callback<Order[]>,
  callbackError?: CallbackError,
) {
  const url = heroId ? `${apiUrl}/orders/${heroId}` : `${apiUrl}/orders`;
  axios
    .get(url)
    .then((response: AxiosResponse<Order[]>) => {
      const orders = parseList<Order>(response);
      callback(orders);
    })
    .catch((error: AxiosError) => {
      console.error(`Developer Error: Async Data Error: ${error.message}`);
      callbackError(`Oh no! We're unable to fetch the Orders`);
    });
};

const getAccountRepCallback = function(
  heroId: number,
  callback: Callback<AccountRepresentative>,
  callbackError?: CallbackError,
) {
  const url = `${apiUrl}/accountreps/${heroId}`;
  axios
    .get(url)
    .then((response: AxiosResponse<AccountRepresentative>) => {
      const list = parseList<AccountRepresentative>(response);
      const accountRep = list[0];
      callback(accountRep);
    })
    .catch((error: AxiosError) => {
      console.error(`Developer Error: Async Data Error: ${error.message}`);
      callbackError(`Oh no! We're unable to fetch the Account Rep`);
    });
};
```


## Promises

Promises do have some indentation to the right, like callbacks. However it tends to not be as extreme. The promise is called to get the Hero and `then` the orders and the account reps are retrieve at the same time using `Promise.all`. 

This is different than the allback technique where each call is made one at a time. `Promise.all` allows you to take the hero data and use it to make two calls: one for orders and one for account reps. When both have returned their responses, the code moves in to the next `then`.

The final step is to merge the orders and account repo data into the Hero.

Notice also, that the nested functions are inside of the `getHeroTreePromise` function. This allows the those functions to access the `hero` variable in the outer function. Otherwise, you'd want to pass the hero around. I prefer this type of closure technique, as it gives those functions context of where they should work (on a hero).

```typescript
const getHeroTreePromise = function(searchEmail: string) {
  let hero: Hero;

  // Level 1 - Get the hero record
  return (
    getHeroPromise(searchEmail)
      // Level 2 - Set the hero, and pass it on
      .then((h: Hero) => {
        hero = h;
        return h;
      })
      // Level 3 - Get the orders and account reps
      .then((hero: Hero) => Promise.all([getOrders(hero), getAccountRep(hero)]))
      // Extract the orders and account reps and put them on their respective Hero objects
      .then((result: [Order[], AccountRepresentative]) => mergeData(result))
  );

  function getOrders(h: Hero): Promise<Order[]> {
    hero = h;
    return h ? getOrdersPromise(h.id) : undefined;
  }

  function getAccountRep(h: Hero): Promise<AccountRepresentative> {
    hero = h;
    return h ? getAccountRepPromise(h.id) : undefined;
  }

  function mergeData(result: [Order[], AccountRepresentative]): Hero {
    const [orders, accountRep] = result;
    if (orders) {
      hero.orders = orders;
    }
    if (accountRep) {
      hero.accountRep = accountRep;
    }
    return hero;
  }
};
```


## Async Await

The async await technique gets the same data, but follows a much more "do this then do that" flow. The code flows line by line, just like synchronous code flows. 

First you get the hero. Then you get the orders and account rep. Notice that you can use the `Promise.all` combined with the async await. This is really helpful as it allows you to make boths calls at the same time, but still "await" their response. Then those responses are merged into the `hero` object.

This code feels the cleanest to me. Less lines and arguably easier to read.


```typescript
const getHeroTreeAsync = async function(email: string) {
  const hero = await getHeroAsync(email);
  if (!hero) return;

  const [orders, accountRep] = await Promise.all([
    getOrdersAsync(hero.id),
    getAccountRepAsync(hero.id),
  ]);
  hero.orders = orders;
  hero.accountRep = accountRep;
  return hero;
};
```


### Nested Async Functions

The functions that the async await function `getHeroTreeAsync` calls are shown below. Here they use axios with the `async` and `await` keywords. The data is retrieved adn then returned.

```typescript
const getHeroAsync = async function(email: string) {
  try {
    const response = await axios.get(`${apiUrl}/heroes?email=${email}`);
    const data = parseList<Hero>(response);
    const hero = data[0];
    return hero;
  } catch (error) {
    handleAxiosErrors(error, 'Hero');
  }
};

const getOrdersAsync = async function(heroId: number) {
  try {
    const response = await axios.get(`${apiUrl}/orders/${heroId}`);
    const data = parseList<Order>(response);
    return data;
  } catch (error) {
    handleAxiosErrors(error, 'Orders');
  }
};

const getAccountRepAsync = async function(heroId: number) {
  try {
    const response = await axios.get(`${apiUrl}/accountreps/${heroId}`);
    const data = parseList<AccountRepresentative>(response);
    return data[0];
  } catch (error) {
    handleAxiosErrors(error, 'Account Rep');
  }
};
```

## Resources

You can learn more about these techniques fro these resources:

* my Pluralsight course [Creating Asynchronous TypeScript Code](https://pluralsight.pxf.io/V9eR6) 
* [source code for my course on GitHub](https://github.com/johnpapa/typescript-async)
* [VS Code and TypeScript](https://code.visualstudio.com/Docs/languages/typescript?wt.mc_id=devto-blog-jopapa)