DEV Community

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

Posted on • Edited 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)