I Promise you won't have to await long to understand async in Javascript
Tags: javascript, webdev, beginners, node
Published: 2021-08-17 10:31:31

---
title: I Promise you won't have to await long to understand async in Javascript
published: true
description: Dealing with Promises shouldn't give you `async`ing feeling. They're how you escape callback hell.
tags: javascript, webdev, beginners, node
canonical_url: https://blog.zack.computer/i-promise-you-will-understand-async-in-javascript
cover_image: https://cdn.hashnode.com/res/hashnode/image/upload/v1629188340364/z8X0txbe-x.jpeg?w=1600&h=840&fit=crop&crop=entropy&auto=compress
---

As you're poking around with modern Javascript, it won't take you long to encounter one of the main asynchronous keywords: `Promise`, `await`, or `async`. So, how do these they work, and why would you want to use them? (And then at the end, some pro-tips for getting the most out of them.)

As with all things in asynchronous programming, we'll answer those questions eventually but the order in which we'll do so is not defined.

```javascript
async function writeBlogPost() {
  await Promise.all([
    writeHowAsyncWorks(),
    writeWhyAsync().then(() => writeAsyncIsNotMultithreading())
  ])
    .then(() => writeProTips())
    .finally(() => writeConclusion());
}
```

## Why Async?

Since the beginning, Javascript has lived on the internet. This necessarily means that it has had to deal with tasks that could take an indeterminate amount of time (usually calls from your device out to a server somewhere). The way that Javascript dealt with this traditionally has been with "callbacks":

```javascript
function getImageAndDoSomething() {
  // This is a simplified example, of course, since arrow functions
  // didn't exist back in the day...
  loadDataFromSite(
    // Function argument 1: a URL
    "http://placekitten.com/200/300",
    // Function argument 2: a callback
    (image, error) => {
      // Do something with `image`
    }
  );
}
```

Callbacks are references to functions that get called when the work is done. Our `loadDataFromSite` function above will call our callback with `image` defined if and when it has successfully loaded the data from the target URL. If it fails, it will call our callback with image set to `null` and, hopefully, `error` defined.

This works fine when you're dealing with simple "get it and do one thing" loops. However, this can quickly enter **callback hell** if you need to do multiple chained calls to a server:

```javascript
function apiCallbackHell() {
  loadData((data, error) => {
    data && transformData(data, (transformed, error) => {
      transformed && collateData(transformed, (collated, error) => {
        collated && discombobulateData(collated, (discombobulated, error) => {
          // And so on...
        })
      })
    })
  })
}
```

This is a mess! Callback hell like this was the motivation behind the [Promise API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which in turn spawned the [async/await API](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await). In a moment we'll break down what this is doing, but for now let's just enjoy how *clean* our function looks with async/await:

```javascript
async function notApiCallbackHell() {
  const data = await loadData();
  const transformed = await transformData(data);
  const collated = await collateData(transformed);
  const discombobulated = await discombobulateData(collated);
  // And so on...
}
```

### Side Quest: Async is not Multithreaded Javascript

Before we break that down, though, let's clarify one common misconception: async code is not the same as multi-threaded code. At its core, Javascript remains a single-threaded environment.

Under the hood of the language is something called the "event loop", which is the engine responsible for reading in a single instruction and performing it. That loop remains a single threaded process - it can only ever read in one instruction at a time and then move on.

Callbacks and Promises make it look like this loop is doing multiple things at once, but it isn't. Let's imagine the instructions in our code as a pile of cards and the event loop is a dealer, pulling them off the top one-at-a-time and stacking them into a neat deck. If we have no callbacks or Promises then the pile our dealer can pull from is clear: it's just what we have in the program, reading through the lines of code top to bottom.

Adding async code to the mix gives our dealer another pile to pull from - the code in our callback or Promise can be read independently from the instructions in the global scope of our program. However, there is still only one dealer (one thread) and they can still only read through one instruction at a time. It's just that now they share their efforts between the different piles. This means that if you put some **very difficult** work into a Promise, you'll be creating a very big new pile for your dealer to pull from. This will slow down the execution of your other code, so interactive UI on your screen might get *verrrrrry* slow as a result.

The solution to this is to move your intense work to another thread - in our metaphor this would be the same as hiring a **second dealer** to sort through the intense pile of instructions separately from our main dealer. How to do that is beyond the scope of this post, but if you're curious check out [Node's Worker Threads](https://blog.logrocket.com/node-js-multithreading-what-are-worker-threads-and-why-do-they-matter-48ab102f8b10/) or [the browser's Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).

## What are the pieces here?

So, we've heard of the main three tools in the async/await landscape, but what do they actually do and how do they work?

### Promise

The backbone of the async/await toolkit is the `Promise` type. `Promise`s are objects. They wrap code that does *something*. Their original purpose was to make it easier to attach callbacks and error handlers to that code. There are several ways to create a promise, but the most basic one is:

```javascript
new Promise((resolve, reject) => {
  // Do something
  if (itSucceeded) {
    resolve(successResult);
  } else {
    reject(failureReason);
  }
});
```

Here you can see the core feature of a `Promise` - it is just a wrapper around callbacks! Inside of the execution block for our new `Promise` we simply have two callbacks - one we should call if the promise successfully did its work (the `resolve` callback) and one we should call if it failed (the `reject` callback).

We then get two functions on the `Promise` that are the most important:

```javascript
const somePromise = getPromise();

somePromise
  .then((result) => {
    // Do something with a success
  })
  .catch((rejection) => {
    // Do something with a rejection
  });
```

`then` and `catch` are extremely useful if you've been handed a `Promise` from some other code. These are how you can attach your own callbacks to the `Promise` to listen for when it resolves (in which case your `then` callback will be called with the resolved value) or to handle a failure (in which case your `catch` callback will be called with the rejection reason, if any).

(Side note there is also a `finally` which, as you might guess, runs after all the `then` and `catch` handlers are finished.)

Then and catch are also useful because they themselves return a `Promise` now containing the return value of your handler.

So, you can use `.then` to chain together multiple steps, partly escaping callback hell:

```javascript
function promisePurgatory() {
  loadData(data)
    .then(data => transformData(data))
    .then(transformed => collateData(transformed))
    .then(collated => discombobulateData(collated))
    .then( /* and so on */ );
}
```

### Async/Await

You might have noticed, though, that `Promise` doesn't completely get us out of needing a huge stack of callbacks. Sure they are now all on the same level, so we no longer need to tab into infinity. But, the community behind Javascript were sure they could do better. Enter `async` and its partner `await`. These two simplify `Promise` programming tremendously.

First of all is `async` - this is a keyword you use to annotate a function to say that it returns a `Promise`. You don't have to do anything further, if you mark a function as `async`, it will now be treated the same as if you'd made it the execution block inside a promise.

```javascript
async function doSomeWork() {
  // Do some complicated work and then
  return 42;
}

async function alwaysThrows() {
  // Oh no this function always throws
  throw "It was called alwaysThrows, what did you expect?"
}

const automaticPromise = doSomeWork();
// Without having to call `new Promise` we have one.
// This will log 42:
automaticPromise.then((result) => console.log(result));

const automaticReject = alwaysThrows();
// Even though the function throws, because it's async the throw
// is wrapped up in a Promise reject and our code doesn't crash:
automaticReject.catch((reason) => console.error(reason));
```

This is by itself pretty useful - no longer do you have to remember how to instantiate a `Promise` or worry about handling both the `reject` case and also any `throw` errors. But where it really shines is when you add in `await`.

`await` can only exist inside of an `async` function, but it gives you a way to pause your function until some other `Promise` finishes. You will then be handed the resolved value of that `Promise` or, if it rejected, the rejection will be thrown. This lets you handle `Promise` results directly without having to build callbacks for them. This is the final tool we need to truly escape callback hell:

```javascript
// From above, now with error handling
async function notApiCallbackHell() {
  try {
    const data = await loadData();
    const transformed = await transformData(data);
    const collated = await collateData(transformed);
    const discombobulated = await discombobulateData(collated);
    // And so on...
  } catch {
    // Remember - if the Promise rejects, await will just throw.
    console.error("One of our ladders out of hell failed");
  }
}
```

## A couple Pro(mise) Tips

Now that you understand the basics of `Promise`, `async`, and `await` a little better, here's a few Pro Tips to keep in mind while using them:

1. **`async` and `.then` will flatten returned `Promise`s automatically.** Both `async` and `.then` are smart enough to know that if you return a `Promise` for some value, your end user does *not* want a `Promise` for a `Promise` for some value. You can return either your value directly or a `Promise` for it and it will get flattened down correctly.

2. **`Promise.all` for joining, not multiple `await`s.** If you have several `Promise`s that don't depend on each other and you want to wait for all of them, your first instinct might be to do:
```javascript
async function waitForAll() {
  // Don't do this
  const one = await doPromiseOne();
  const two = await doPromiseTwo();
  const three = await doPromiseThree();
}
```
This is going to cause you problems, though, because you're going to wait for promise one to finish before you start promise two, and so on. Instead, you should use the built-in function `Promise.all`:
```javascript
async function waitForAll() {
  const [one, two, three] = await Promise.all([
    doPromiseOne(), doPromiseTwo(), doPromiseThree()
  ]);
}
```
This way your code will create all three promises up front and run through them simultaneously. You're still going to `await` all three finishing, but it will take much less time because you can spend downtime on promiseOne working on promiseTwo or Three.

3. **`Promise.allSettled` if failure is acceptable.** The downside of `Promise.all` or serial `await`s is that if one of your `Promise`s reject, then the whole chain is rejected. This is where `Promise.allSettled` comes in. It works the same as `Promise.all` except that it will wait until all the arguments have resolved **or** rejected and then pass you back an array of the `Promise`s themselves. This is useful if you're trying to do some work but it's ok if it fails.

4. **Arrow functions can be `async` too.** Last but most certainly not least, it's important to keep in mind that arrow functions can be marked as `async` too! This is really really useful if you're trying to create a callback handler where you'll want to use `await`, such as for an `onSubmit` for a form:
```jsx
// Imagining we're in react...
return <Form onSubmit={
  async (values) => {
    const serverResponse = await submitValuesToServer(values);
    window.location.href = "/submitted/success";
  }
}>{/* Form contents */}</Form>
```

## .finally(...)

Let me know in the comments down below what questions you now have about `Promise`, `async`, and `await`. Even though I use these three in every Node and React app I write, there are still tons of nuances to learn about them.

If you enjoyed this, please leave me a like, and maybe check out my last "back to basics" article on [the ins and outs of `this` in JS](https://blog.zack.computer/taming-this-in-javascript).