Error Handling and async-await
I would like to start this post by asking you the following question:
What does this piece of code:
async function asyncCall() { setLoading(true); const response = await getApiRequest(); setLoading(false); return response.data;}
have in common with a never-ending spinner?
During a typical day at work, sometimes I go over other people’s code for inspiration (not really). Over time, I find myself stumbling across async-await
syntax, and barely Promise..then
syntax. Don’t get me wrong, I’m not against async-await
syntax. But, when we usually use the await syntax, we’re often not devoting enough time handling errors that might pop out. I’ll give you an example — When I asked my co-worker, “What if the promise will fail?” he responded, “It’s okay, I wrote the server-side. It should never fail”. Guess what? Two days after, someone else touched the server code, and we got a blank screen on the demo with a client. I’ll give you another example — When I asked a different co-worker, “Why not just use Promise..then for this scenario?” then he responded, “Well… it looks better like that”.
So, the answer to my question is — Unhandled promise rejection.
I’m not against using async-await
as long as we take into consideration that a promise will fail at some point. There are few ways of handling these rejections cleanly, and I’ll show some of them:
1. Promise…then…catch
Simply replace the await
with the “good-old” syntax which allows you to handle promise rejections out of the box:
function asyncCall() {
setLoading(true);return getApiRequest()
.then(response => response.data)
.catch(response => showErrorNotification(response)
.finally(() => setLoading(false);}
2. Wrap inside an async handler function
This is a custom solution which I found quite useful. Lately, I found out about react-query, which pretty much supports this kind of solution and even complicated:
function handleAsync(promise) {
return promise
.then(data => ({data, error: null}))
.catch(error => ({data: null, error}))
}...function asyncCall() { setLoading(true); const { data, error } = await handleAsync(getApiRequest()); setLoading(false); if (error !== null) {
showErrorNotification(error);
} return data
}
3. Try…catch
I’m usually not working with try/catch. Although it might be useful for some others:
async function asyncCall() {
setLoading(true);
try {
const response = await getApiRequest();
} catch (error) {
showErrorNotification(error);
} finally {
setLoading(false);
}}
Conclusion
async-await
has made our lives much easier. Although, it feels like we never take the time to actually capture the exceptions that might come from the awaited promises. Don’t let your customers wait for a never-ending spinner :)