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)