One of the things that took the Javascript community by storm was the introduction of async-await. It was simple and looked a lot better than the then-catch of Promises and also more readable and debuggable than the callback hell. But one thing that bothered me was the usage of try-catch. At first I thought it isn't a problem but as fate would have it I was working on chained API calls and the problem cropped up where each API call has specific error message that had to be printed. I realized soon that I was creating a try-catch hell.
Let's consider this Promise that resolves or rejects after 2 seconds based on a parameter rejectPromise
// api.js
const fetchData = async (duration, rejectPromise) => (
new Promise((resolve, reject) => {
setTimeout(() => {
if (rejectPromise) {
reject({
error: 'Error Encountered',
status: 'error'
})
}
resolve({
version: 1,
hello: 'world',
});
}, duration);
})
);
module.exports = {
fetchData,
};
So my typical usage of async-await is gonna be like this.
const { fetchData } = require('./api');
const callApi = async () => {
try {
const value = await fetchData(2000, false);
console.info(value);
} catch (error) {
console.error(error);
}
}
callApi();
/*
OUTPUT:
{ version: 1, hello: 'world' } (rejectPromise=false)
{ error: 'Error Encountered', status: 'error' } (rejectPromise=true)
*/
As you can see when the rejectPromise
parameter is false
the await resolves to { version: 1, hello: 'world' }
and when it's true
it rejects the promise and catch is called and the error is { error: 'Error Encountered', status: 'error' }
.
That's the typical implementation of async-await. Now we will leverage the promise functions then-catch to make the process simpler. Let's write a wrapper that does this.
// wrapper.js
const wrapper = promise => (
promise
.then(data => ({ data, error: null }))
.catch(error => ({ error, data: null }))
);
module.exports = wrapper;
We can see that the wrapper takes a promise as an input and returns the resolved/rejected values through then-catch. So let's go and modify the original code we wrote in try-catch to utilize the wrapper.
const { fetchData } = require('./api');
const wrapper = require('./wrapper');
const callApi = async () => {
const { error, data } = await wrapper(fetchData(2000, false));
if (!error) {
console.info(data);
return;
}
console.error(error);
}
callApi();
/*
OUTPUT:
{ version: 1, hello: 'world' } (rejectPromise=false)
{ error: 'Error Encountered', status: 'error' } (rejectPromise=true)
*/
Voila the same output but the this way makes it better to understand the code.
Top comments (8)
Nice!
FYI: You might still need try..catch though, the wrapper catches asynchronous errors but if inside
callApi
anything you call issues a "standard" error that exception will bubble up to the user.Thanks! :) And Yeah on regular exceptions I would need a catch but I was focusing on the API part only! :)
I like it, it reminds me of how Go does error handling :D
I still haven't worked with Go yet 🙈 planning to start soon.
Already existed github.com/scopsy/await-to-js and my version github.com/ymatuhin/flatry (I just like you didn't know about await-to-js).
Didn't know about that. Thanks for sharing :)
Shouldn't the wrapper have a return before promise? Else it would return undefined
In ES5 you can write a function like this
const foo = args => 'bar';
and it will return bar when invoked. This is known as implicit return when it spans multiple lines you enclose it with in( /* code */ )
.