The saga of Javascript errors and async/await
Tags: javascript, async
Published: 2021-07-16 12:33:35

We went full circle with error management in Javascript!

Long ago, before AJAX was a thing, JS was pretty much synchronous and linear. Still, we had to handle errors, so we used `try/catch`:

```js
function divide(a, b) {
  return a / b;
}

try {
  divide(1, 0);
} catch (error) {
  // uh, oh
}
```

But then Ajax came along, along with a lot of other async API's. We started using more callbacks for our error management:

```js
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (content) { /* ... */ };
xhr.onerror = function (error) { /* ... */ };
```

And then Node.JS came along, with a callback based API. Error handling was quickly turning into `callback hell`:

```js
fs.readFile('foo.txt', function (error, fileContent) {
  fs.writeFile('bar.txt', fileContent, function (otherError) {
    // ...
  });
});
```

And then... Then we created `Promise` to make it more readable and consistent (apparently):

```js
// Fetch full content of the first item found
fetch('www.example.com/items')
  .then(response => response.json())
  .then(response => 
    fetch('www.example.com/items/' + response[0].id)
  )
  .catch(error => {
    // Wait! Is this from the first or the second request?
  });
```

A bit better, but we still need sequential execution, and soon we get into `promise hell`.

## async/await to the rescue

Okay, we need to fix this.
So we decided to introduce a new syntax that will internally wait for a promise resolution and clean up the code. `async/await` is the solution:

```js
// Fetch full content of the first item found
const items = await fetch('www.example.com/items')
const firstItem = (await items.json())[0]
const fullContent = await fetch('www.example.com/items/' + firstItem.id)
```

But wait! What if the first or the second request fails?
Don't worry! We have a solution for that too!

Behold the full circle of errors: `try/catch` 😆

```js
try {
  // Fetch full content of the first item found
  const items = await fetch('www.example.com/items')
  const firstItem = (await items.json())[0]
  const fullContent = await fetch('www.example.com/items/' + firstItem.id)
} catch (error) {
  // Wait! Is this from the first or the second request?
}
```

## Pitfalls, pitfalls everywhere!

Cool! Try/catch is now async. YAY!

But what happens if I forget to use await? Well, more errors for you!

If you actually want to return a promise from a function but still handle errors, it looks like this:

```js
try {
  return fetchAndParse('www.example.com/items.json')
} catch (error) {
  // synchronous errors end up here
}
```

Can you guess what happens if `fetchAndParse` returns a rejected promise?

```js
function fetchAndParse() {
  return new Promise((resolve, reject) => {
    reject(new Error('Nope!'));
  };
}
```

And the answer is: **nothing**. Your `try/catch` block won't do anything to catch that error. 
Why? Because you need the magic word in front of the function call to "connect" the chain of promises:

```js
try {
  return await fetchAndParse('www.example.com/items.json')
} catch (error) {
  // the rejection error ends up here
}
```

See the difference? Subtle, but quite important!

## Conclusion

So basically, if you want to use async/await, you need to wrap everything with try/catch, and you must ensure every single promise has an `await` in front of it.

Otherwise, JS will just ignore the errors and continue like nothing happened.