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
}
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) { /* ... */ };
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) {
// ...
});
});
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?
});
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)
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?
}
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
}
Can you guess what happens if fetchAndParse
returns a rejected promise?
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:
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.
Top comments (0)