arkm
Share

This one weird trick changed my whole async/await game

If you're anything like me then async and await were a revelation when they got added to JavaScript. Finally, there was a solution to the endless callbacks and chained then()s. It enabled us to write synchronous looking code with all the goodness asynchronicty provides.

But very quickly you might have noticed an issue handling rejected Promises. To address it you probably tackled it in one of two way:

Method 1 The simplest method was to just wrap everything in a try/catch block.

try {
  const res = await fetch("...");
  const data = await res.json();
} catch (err) {
  console.log(err);
}

I found using this method often resulted in me dumping huge amounts of code within the try block. This meant that every error would result in all the code in the try not being run which wasn't always what I would want; sometimes I would want to handle an error by simply logging the error to the console and moving on with the rest of the code.

This brings to the second method.

Method 2 With this method you handle the error handling on a per-Promise-basis by chaining a catch().

const res = await fetch("...").catch((err) => console.log(err));

The problem here is a matter of developer experience. It SUCKS to keep writing a .catch() at the end of everything. And, honestly, who's got that kind of time?

And this brings me to the method that I use: the third method.

Method 3 With this method, we go back to our trusty try/catch block, but this time it's in a handy helper function. We are are going to make use of array destructuring to make the error a first-class citizen along with the desired resolution from the Promise.

const [data, err] = await call(fetch("..."));
if (err) console.log(err);

async function call(promise) {
  let data, error;
  try {
    const res = await promise;
    data = await res.json();
  } catch (err) {
    error = err;
  }
  return [data, error];
}

Using this method we relegate our Promise rejection and resolution handling to the call() function. In this case, I even have it handling the .json() for me so I can use fetch and immediately get the JSON data I care about. This helper function returns an array where the first item is the data from a resolved Promise and the second item is the err from the rejected Promise. If the Promise resolved, the error will be undefined; if the Promise rejects the data will be undefined.

This let's me write very clean, synchronous looking code. It make handling error a breeze as I can easily either log the error and move on or provide some complex fallback scenario if I need to.

Since I've been writing all my async/await code this I've found that I've been able to write much more complex applications in less time, with fewer bugs, and with richer error-handling than I did previously.