DEV Community

Cover image for The saga of Javascript errors and async/await
Darlan Alves
Darlan Alves

Posted on • Updated on

The saga of Javascript errors and async/await

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:

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

try {
  divide(1, 0);
} catch (error) {
  // uh, oh
}
Enter fullscreen mode Exit fullscreen mode

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

const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (content) { /* ... */ };
xhr.onerror = function (error) { /* ... */ };
Enter fullscreen mode Exit fullscreen mode

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

fs.readFile('foo.txt', function (error, fileContent) {
  fs.writeFile('bar.txt', fileContent, function (otherError) {
    // ...
  });
});
Enter fullscreen mode Exit fullscreen mode

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

// 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?
  });
Enter fullscreen mode Exit fullscreen mode

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:

// 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)
Enter fullscreen mode Exit fullscreen mode

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 😆

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?
}
Enter fullscreen mode Exit fullscreen mode

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:

try {
  return fetchAndParse('www.example.com/items.json')
} catch (error) {
  // synchronous errors end up here
}
Enter fullscreen mode Exit fullscreen mode

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

function fetchAndParse() {
  return new Promise((resolve, reject) => {
    reject(new Error('Nope!'));
  };
}
Enter fullscreen mode Exit fullscreen mode

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:

try {
  return await fetchAndParse('www.example.com/items.json')
} catch (error) {
  // the rejection error ends up here
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)