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 */ )
.