“To kill an error is as good a service as, and sometimes even better than, the establishing of a new truth or fact.”
― Charles Darwin, More Letters of Charles Darwin, Vol 2
Introduction
In the last post, we learned about asynchronous functions. Next, we will learn about how to handle the errors in asynchronous functions. This is important because the user needs to know whether the operation the user does is successful or not as soon as possible. No one likes to be given false hopes, right?
Handling Asynchronous Function Errors
First of all, there are two common ways in creating an asynchronous function. The first one is by using Promise
and second one is using async-await
.
A catch()
function will be executed if either the promise rejects or an async function throws an error. But, how to use the catch()
is different between Promise
and async-await
.
We will use the code snippet below as the base to build a function with the same logic called addAsync()
, then handle the error in some ways:
const callAsync = async () => {
addAsync(1,3); // handle the errors here.
};
callAsync();
Then, we write the addAsync()
function:
const addAsync = (a, b) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (a + b < 5) {
resolve(a+b);
}
else {
reject('Must be less than 5!');
}
}, 1000);
});
};
The addAsync
function returns a new promise which would resolve if the sum of its input is less than 5, and reject if it’s more than 5.
Promise-then-catch
The result of addAsync()
is a promise. To get the resolved result, we need to call .then()
and .catch()
to catch the error if it happens. Let’s modify callAsync()
:
const callAsync = async () => {
const promise = addAsync(1,3);
promise
.then((result) => {
console.log("Result:", result); // "Result:", 4
})
.catch((error) => {
console.log("Error:", error); // "Error:", "Must be less than 5!"
});
};
This is a promise’s way to handle the error. It’s simple.
Try-catch
What about the error handling way of async-await
? Be patient, you are reading it now. We just need to modify callAsync()
also, but in a different way:
const callAsync = async () => {
try {
const result = await addAsync(1,3);
console.log("Result:", result); // "Result:", 4
}
catch(err) {
console.log("Error:", error); // "Error:", "Must be less than 5!"
}
};
Add an await
before addAsync()
, then wrap it in a try-catch. Done. When the promise resolves, it will continue on the try
block. But, if the promise rejects, it will be caught as an error in the catch
block.
Wrap inside an asynchronous handler function
To put it simply, this solution’s concept is to put either the promise-then-catch
or try-catch
error handling inside an asynchronous function.
By using this handler solution, the code in callAsync()
is much more readable. See this snippet below:
const asyncHandler = async (asyncFunc) => {
try {
const result = await asyncFunc;
return result;
}
catch(err) {
return err;
}
};
const promiseHandler = (promise) => {
return promise
.then((result) => {
return result;
})
.catch((error) => {
return error;
});
};
We created two new functions here with the same logic. The asyncHandler()
for the async-await
way, and promiseHandler()
for the promise
way. Both codes work in the same way when executed. Let’s modify callAsync()
for both functions:
const callAsync = async () => {
const result = await asyncHandler(addAsync(1,3));
console.log(result); // "4"
const result2 = await asyncHandler(addAsync(1,7));
console.log(result2); // "Must be less than 5!"
// ------------------------------------------------
const result3 = await promiseHandler(addAsync(1,1));
console.log(result3); "4"
const result4 = await promiseHandler(addAsync(1,8));
console.log(result4); // "Must be less than 5!"
};
callAsync();
In callAsync()
, both functions are executed by calling await asyncHandler(addAsync())
and await promiseHandler(addAsync())
.
The result of addAsync()
is handled by the generic function of either asyncHandler()
or promiseHandler()
. By using these generic handler functions, there is no need to write more promise-then-catch
or try-catch
for handling the errors. And the result is as you can see, if the main logic is in callAsync()
, it becomes much more readable.
Which is better? Personally, I prefer async-await, to keep the consistency with the rest of the code. Because while the promiseHandler()
can be called using await
, the function itself does not need to be async
.
Conclusion
Technically, handling errors are not difficult. What’s difficult is remembering to handle the errors, what kind of errors we expect to happen, and expecting the unexpected errors.
I hope you learned something from this post. And let me know about your experiences on handling errors in asynchronous functions.
Live your code and code your life!
“But slight mistakes accumulate, and grow to gross errors if unchecked.”
― Jacqueline Carey, Kushiel’s Chosen