Author avatar
Fatih Kalifa
Imagineer, whatever that is
Feb 023 min read
    Share

Promise: The Less Interesting Part

ES2015 has already became mainstream and it brings many features to JavaScript language. One of the notable features is Promise for handing asynchronous tasks.

While Promise is definitely useful feature, I still see some people confused about the semantic of promise resolution and error handling. Here's a few examples that I've seen people trip over so far.

Fulfillment value

Once promise is resolved, the value stays the same.

const a = Promise.resolve(10);

a.then(() => 20);
a.then(() => throw new Error('something')).catch(() => 30);

a.then(ret => console.log(ret)); // will print 10

If you want to use the return value from the next promise handler, you have to assign them to another variable. But the original value stays the same.

const a = Promise.resolve(10);
const b = a.then(() => 20);
const c = a.then(ret => ret + 20);
const d = b.then(ret => ret + 20);

a.then(ret => console.log(ret)); // will print 10
b.then(ret => console.log(ret)); // will print 20
c.then(ret => console.log(ret)); // will print 30
d.then(ret => console.log(ret)); // will print 40

Error handling

Browsers and node.js will warn you if you have any missing rejection handler. This is not a bug, you should always handle your rejected promise. You can do that using onRejected or .catch. Please keep in mind that these two methods are different, but can be similar in some cases.

onRejected handler is passed as a second argument in .then method:

promise.then(onFulfilled, onRejected);

If you don't put onRejected handler, the behavior will be similar to this:

promise.then(onFulfilled, err => throw err);

Which will propagate the error through the promise handler and causing uncaught rejection error, unless you handle the error in the next promise handler.

You can also use .catch method to handle error from previous promise:

promise.catch(errorCallback);

Now, you might think that those two method behave the same way, but they don't.

Consider this example:

const samplePromise = new Promise((resolve, reject) => {
  // some async fn
});

// Error handling #1
samplePromise.then(successCallback, errorCallback);

// Error handling #2
samplePromise.then(successCallback).catch(errorCallback);

In the first case, errorCallback only handles rejection (or error) from samplePromise. If successCallback throws an error, errorCallback won't catch them and you will get uncaught rejection error. You still need to put .catch if you want to handle error coming from successCallback.

In the second case, errorCallback will catch error from Promise resolved by successCallback. Because the .then handler only include 1 argument (only handle fulfilled state), if samplePromise is rejected, the error will propagate through and will be handled by .catch method. 

By knowing this you can use .catch to act as catch all error:

promise
.then(doSomething)
.then(doSomethingElse)
.then(doAnything)
.catch(handleError)

The .catch handler will handle error / rejection in promise, doSomething, doSomethingElse and doAnything.

To recap, onRejection in .then handler only handle error in the exact previous promise, while .catch will handle error in all previous promises.

Error handler return value is resolved

When you specify onRejected handler (or .catch) the return value from the callback will be used as fulfilled state for the next promise

const pr = Promise.reject(new Error('random'));

pr
.then(() => 42, () => 55)
.then(value => console.log(value)); // will print 55

// or similar
pr
.catch(() => 55)
.then(value => console.log(value)); // will print 55

Even when you don't specify any return value, when the rejected promise is handled by onRejected or .catch, it will still be resolved to undefined.

const api = fetch('http://api.example.com/users/1')
.then(res => {
  return res.json();
}, err => {
  // data fetching failed, send to error reporting
  Raven.captureException(err);
});


api.then(data => {
  // data will be undefined if there is an error when fetching data
});

Next time you handle an error in Promise, give meaningful return value or make sure it's the last error handler (so there's no more promise handler) to prevent unnecessary surprise.

  • promise
  • javascript
  • es2015
  • gotcha