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.

Top comments (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 • Edited

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

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

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

Collapse
 
prateek1712 profile image
Prateek Jesingh • Edited

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

Collapse
 
sadarshannaiynar profile image
Adarsh

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