Error handling in JavaScript can be easy while being tricky at certain places, especially Promises. JS allows error handling with the help of try, catch, and throw.
const main = () => {
try {
// Do something super crazy
if (!bakePizza()) {
throw new Error('Oh no!');
}
} catch (error) {
// That escalated quickly
handleOvenExplosion();
}
}
This seems simple enough but gets a little tricky when Promises are involved.
Let’s look at a simple example of a Promise. The following Promise function fetches a list of user profiles from the database, where the result set is resolved by the promise function and the error is rejected.
const userProfileQuery = new Promise((resolve, reject) => {
connection.query('SELECT * FROM Users', [], (err, result) => {
if (err) reject({ type: 'SQL', err});
connection.release();
resolve(result);
});
userProfileQuery
.then((data) => {
const userList = data;
// Do something crazy with the list
})
.catch((err) => {
// Oh, snap!
// Handle error
});
In an ideal world, we’d want to have a single try-catch block to handle all errors that occur in that single file.
const { getUserProfiles } = require('./helpers');
module.exports = () => {
try {
let userProfileList;
getUserProfiles
.then((data) => {
userProfileList = data;
})
.catch((error) => {
// Handle Promise Error
// All errors thrown in this promise land here
});
} catch (error) {
// Handle errors in this module
}
}
The above module is simple — It fetches a list of user profiles with the help of a Promise function.
But the problem with the above module is that when we throw
a new Error
inside the then
block of the promise, it will always pass to the catch
block of the promise. That is because throwing a new error inside a then
block of a promise will always be passed to the catch
block of the invoking promise function. This does not allow us to handle all errors in a module with a singular try-catch block.
But, alas! There is a way to handle this with the help of Async/Await. Let me explain this better with an example —
const { getUserProfiles } = require('./helpers');
module.exports = async () => {
try {
const userProfileList = await getUserProfiles;
} catch (error) {
// Handle errors in this module
switch (type) {
case ERROR_SQL:
// Handle SQL errors
default:
// Handle common errors
}
}
}
This little addition of async/await in your code does two things —
Assign the value to the variable which was resolved by the promise function.
Throw error if the promise function rejects anything.
Note that the value assignment works only when a promise functions resolves some value and errors get thrown only when the promise function rejects something.
This way, async/await lets us keep our code clean, maintainable, and easy to read.
Thanks for reading. If you have thoughts on this, be sure to leave a comment.