«

Synchronous vs Asynchronous Errors

The asynchronous coding styles used in Javascript can be non-intuitive for developers more familiar with imperative languages. That can be dangerous because things look just familiar enough that people can make dangerous assumptions. That’s compounded by the fact that Javascript has evolved from callbacks, to promises, to async/await very rapidly. It can get complicated if you have to work with code written in a mix of the three different styles.

I’ve seen several instances where developers wrote code using Promises and assumed that their catch function call would catch any possible errors.

Here’s a simplified example:

function doSomethingAsync() {
  throw new Error('synchronous');

  // we'll never get here
  return new Promise(resolve => setImmediate(() => resolve()));
}

doSomethingAsync()
  .then(() => console.log('No error!'))
  .catch((err) => {
    // we expect to get any errors here
    console.error(`Do Something very important! ${err}`);
  });

Clearly the person who wrote this code assumes that since doSomething is expected to return a promise then any errors that occur will be caught in the catch function call. In fact there might be lots of ways for doSomething to fail that have nothing to do with it’s asynchronous work. Since Javascript is interpreted we might even get an error for a simple typo or other syntax error.

If we run the code shown above we get:

Error: synchronous

Not what we might have expected. Since the error in our function is not actually using promises the catch block will never see this error. That can be a big problem if we expect that something important will happen in case of any errors occurring.

Even worse, this can be a hard bug to catch if you’re not looking for it.

In the past I would fix code like this by wrapping the entirety of doSomething in a try/catch block like:

function doSomethingAsync() {
  try {
    throw new Error("synchronous");

    // we'll never get here
    return new Promise(resolve => setImmediate(() => resolve()));
  } catch (err) {
    // now our function will always return a promise
    return Promise.reject(err);
  }
}

doSomethingAsync()
  .then(() => console.log("No error!"))
  .catch((err) => {
    console.error(`Do Something very important! ${err}`);
  });

That gives us the expected behavior:

Do Something very important! synchronous

Now that async/await is available in Javascript we can handle this without using that try/catch:

async function doSomethingAsync() {
  throw new Error("synchronous");

  // we'll never get here
  return new Promise(resolve => setImmediate(() => resolve()));
}

doSomethingAsync()
  .then(() => console.log("No error!"))
  .catch((err) => {
    console.error(`Do Something very important! ${err}`);
  });

Much nicer!