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 10b.then((ret) => console.log(ret)); // will print 20c.then((ret) => console.log(ret)); // will print 30d.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 #1samplePromise.then(successCallback, errorCallback);// Error handling #2samplePromise.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 similarpr.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.