5 Async/Await Design Patterns for Cleaner Async Logic
Tags: javascript, node, vue, async
Published: 2021-06-25 17:26:41

At [Mastering JS](https://masteringjs.io/), we love [async/await](https://masteringjs.io/tutorials/fundamentals/async-await). You might even say we wrote the [book on async/await](https://asyncawait.net/). Here's 5 design patterns we use regularly.

## Async [`forEach()`](https://masteringjs.io/tutorials/fundamentals/foreach)

Do **not** use an async [callback](https://masteringjs.io/tutorials/fundamentals/callbacks) with `forEach()`. In general, the way to simulate [`forEach()` with async functions](https://masteringjs.io/tutorials/fundamentals/async-foreach) is to use `await Promise.all([arr.map(callback)])`

```javascript
const values = [10, 50, 100];

// Do this:
await Promise.all(values.map(async v => {
  await new Promise(resolve => setTimeout(resolve, v));
  console.log('Slept for', v, 'ms');
}));

// Not this:
values.forEach(async v => {
  await new Promise(resolve => setTimeout(resolve, v));
  console.log('Slept for', v, 'ms');
});
```

## `return await`

Async/await works with `try/catch`... almost. There's a gotcha. If you `await` on a promise that rejects, JavaScript throws an error that you can `catch`. But if you `return` a promise that rejects, that ends up as an [unhandled promise rejection](https://thecodebarbarian.com/unhandled-promise-rejections-in-node.js.html).

```javascript
const p = Promise.reject(new Error('Oops!'));

try {
  await p;
} catch (err) {
  console.log('This runs...');
}

try {
  return p;
} catch (err) {
  console.log('This does NOT run!');
}
```

There are a few workarounds for this quirk, but one approach we like is using `return await`.

```javascript
try {
  return await p;
} catch (err) {
  console.log('This runs!');
}
```

## Delayed `await`

Sometimes you want to call an [async function](https://thecodebarbarian.com/async-functions-in-javascript.html), do something else, and then `await` on the async function. Promises are just variables in JavaScript, so you can call an async function, get the promise response, and `await` on it later.

```javascript
const ee = new EventEmitter();

// Execute the function, but don't `await` so we can `setTimeout()`
const p = waitForEvent(ee, 'test');

setTimeout(() => ee.emit('test'), 1000);

// Wait until `ee` emits a 'test' event
await p;

async function waitForEvent(ee, name) {
  await new Promise(resolve => {
    ee.once(name, resolve);
  });
}
```

## `await` with [Promise Chaining](https://masteringjs.io/tutorials/fundamentals/promise-chaining)

We recommend using [Axios](https://masteringjs.io/axios) over `fetch()`, but in some cases you may need to use `fetch()`. And `fetch()` famously requires you to asynchronously parse the response body. Here's how you can make a request with `fetch()` and parse the response body with 1 `await`.

```javascript
const res = await fetch('/users').then(res => res.json());
```

Another quirk of `fetch()` is that it doesn't throw an error if the server responds with an error code, like 400. Here's how you can make `fetch()` throw a catchable error if the response code isn't in the 200 or 300 range.

```javascript
const res = await fetch('/users').
  then(res => {
    if (res.status < 200 || res.status >= 400) {
      throw new Error('Server responded with status code ' + res.status);
    }
    return res;
  }).
  then(res => res.json());
```

## Waiting for Events

Event emitters are a common pattern in JavaScript, but they don't work well with async/await because they're not [promises](https://masteringjs.io/tutorials/fundamentals/promise). Here's how you can `await` on an event from a Node.js event emitter.

```javascript
const ee = new EventEmitter();

setTimeout(() => ee.emit('test'), 1000);

// Wait until `ee` emits a 'test' event
await new Promise(resolve => {
  ee.once('test', resolve);
});
```