DEV Community

Adarsh
Adarsh

Posted on

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

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.

Discussion (8)

Collapse
rhymes profile image
rhymes

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.

const callApi = async () => {
  const { error, data } = await wrapper(fetchData(2000, false));
  callAFunctionThatDividesByZero()
  if (!error) {
    console.info(data);
    return;
  }
  console.error(error);
}
Collapse
sadarshannaiynar profile image
Adarsh Author • Edited on

Thanks! :) And Yeah on regular exceptions I would need a catch but I was focusing on the API part only! :)

Collapse
rhymes profile image
rhymes

I like it, it reminds me of how Go does error handling :D

Thread Thread
sadarshannaiynar profile image
Adarsh Author

I still haven't worked with Go yet 🙈 planning to start soon.

Collapse
ymatuhin profile image
Yury Matuhin

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

Collapse
sadarshannaiynar profile image
Adarsh Author

Didn't know about that. Thanks for sharing :)

Collapse
prateek1712 profile image
Prateek Jesingh • Edited on

Shouldn't the wrapper have a return before promise? Else it would return undefined

Collapse
sadarshannaiynar profile image
Adarsh Author

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