Capture error and data in async-await without try-catch

Adarsh - Apr 30 '18 - - Dev Community

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,
};
Enter fullscreen mode Exit fullscreen mode

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)

 */
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)

 */
Enter fullscreen mode Exit fullscreen mode

Voila the same output but the this way makes it better to understand the code.

. . . . . . . . .