Read more

JavaScript: Don't throw synchronous exceptions from functions that return a Promise

Henning Koch
January 10, 2017Software engineer at makandra GmbH

This case cannot happen when you write an async function() instead of returning promises explicitly.

TLDR: A function is hard to use when it sometimes returns a promise and sometimes throws an exception. When writing an async function, prefer to signal failure by returning a rejected promise.

The full story

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more Show archive.org snapshot

When your function returns a promise ("async function"), try not to throw synchronous exceptions when encountering fatal errors.

So avoid this:

function foo(x) {
  if (!x) {
    throw "No x given"
  } else
    return new Promise(function(resolve, reject) {
      ...
    });
  }
}

It's hard to handle errors when calling foo, since we need to handle both a synchronous throw and an asynchronous rejection. Look how much code we need to handle failures of the foo() above:

try {
  function onFulfilled() { ... }
  function onRejected() { // handle async failure }
  foo(x).then(onFulfilled, onRejected);
} catch (e) {
  // handle synchronous failure
}

Instead you should signal an error by returning a rejected promise:

function foo(x) {
  if (!x) {
    return Promise.reject("No x given")
  } else
    return new Promise(function(resolve, reject) {
      ...
    });
  }
}

Now your caller only needs to handle async failure:

function onFulfilled() { ... }
function onRejected() { // handle async failure }
foo(x).then(onFulfilled, onRejected);

Converting exceptions to rejected promises

If you have an existing function that throws synchronous errors, and you want to convert all these throws to rejected promises, you can make the function async:

async function foo() {
  throw "error"
}

foo() // returns Promise.reject("error")
await foo() // throws "error"

As an alternative you can use the behavior that exceptions thrown in promise callbacks will automatically be caught and converted to a rejected promise instead. That means if you really want to throw from foo() (or if you're calling another function that throws) you can wrap your code into the callback of a resolved promise:

function foo(x) {
  var promise = Promise.resolve();
  return promise.then(function() {
    if (!x) {
      throw "No x given"
    } else
      return new Promise(function(resolve, reject) {
         ...
      });
    }
  });
}

This behavior is in all Promises/A+ Show archive.org snapshot compatible implementations like native promises, jQuery 3+ deferreds or Bluebird. Deferreds in jQuery 1 and 2 do not have this behavior.

Read more

Promise-based functions should not throw exceptions Show archive.org snapshot

Henning Koch
January 10, 2017Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2017-01-10 21:56)