When to reject/resolve a promise

Christopher Will picture Christopher Will · Jun 25, 2013 · Viewed 13.2k times · Source

I am thinking about when exactly I need to reject a promise. I found a couple of questions regarding this topic, but could not find a proper answer. When should I reject a promise?

This article http://howtonode.org/6666a4b74d7434144cff717c828be2c3953d46e7/promises says:

  • Resolve: A successful Promise is 'resolved' which invokes the success listeners that are waiting and remembers the value that was resolved for future success listeners that are attached. Resolution correlates to a returned value.
  • Reject: When an error condition is encountered, a Promise is 'rejected' which invokes the error listeners that are waiting and remembers the value that was rejected for future error listeners that are attached. Rejection correlates to a thrown exception.

Is this the principle guideline? That one only reject a promise if an exception occured?

But in case of a function like

findUserByEmail()

I'd would expect the function to return a user, so that I can continue the chain without verifying the result

findUserByEmail()
    .then(sendWelcomeBackEmail)
    .then(doSomeNiceStuff)
    .then(etc..)

What are best / common practises?

Answer

ForbesLindesay picture ForbesLindesay · Jun 25, 2013

In general you can think of rejecting as being analogous to a synchronous throw and fulfilling as being analogous to a synchronous return. You should reject whenever the function is unsuccessful in some way. That could be a timeout, a network error, incorrect input etc. etc.

Rejecting a promise, just like throwing an exception, is useful for control flow. It doesn't have to represent a truly unexpected error; it can represent a problem that you fully anticipate and handle:

function getProfile(email) {
  return getProfileOverNetwork(email)
    .then(null, function (err) {
      //something went wrong getting the profile
      if (err.code === 'NonExistantUser') {
        return defaultUser;
      } else if (profileCached(email)) {
        return getProfileFromCache(email);//fall back to cached profile
      } else {
        throw err;//sometimes we don't have a nice way of handling it
      }
    })
}

The rejection lets us jump over the normal success behavior until we get to a method that knows how to handle it. As another example, we might have some function that's deeply nested at the bottom of the applications stack, which rejects. This might not be handled until the very top of the stack, where we could log it. The point is that rejections travel up the stack just like exceptions do in synchronous code.

In general, wherever possible, if you are struggling to write some asynchronous code, you should think "what would I write if this were synchronous". It's usually a fairly simple transformation to get from that, to the promised equivalent.

A nice example of where rejected promises might be used is in an exists method:

function exists(filePath) {
  return stat(filePath) //where stat gets last updated time etc. of the file
    .then(function () { return true; }, function () { return false; })
}

Notice how in this case, the rejection is totally expected and just means the file does not exist. Notice also how it parallels the synchronous function though:

function existsSync(filePath) {
  try {
    statSync(filePath);
    return true;
  } catch (ex) {
    return false;
  }
}

Your Example

Returning to your example:

I would normally chose to reject the promise resulting from findUserByEmail if no user was found. It's something you fully expect to happen sometimes, but it's the exception from the norm, and should probably be handled pretty similarly to all other errors. Similarly if I were writing a synchronous function I would have it throw an exception.

Sometimes it might be useful to just return null instead, but this would depend on your applications logic and is probably not the best way to go.