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