JavaScript Promises: Explaining then & catch to a 5 year old.

RMAG news

1. Promise.catch() is not try{}…catch(){}.

The .catch() method of promise is just .then(void 0, onRejected). It may seem like it “catches” some errors, but that is just because of the special handling of .then() logic.

2. Promise.then()

A confusing quirk of .then(onFulfilled, onRejected), is if EITHER of the callbacks throw an error, it’s promise will be rejected with the error. For example:

var promise = new Promise(function(resolve, reject){
setTimeout(reject, 1000);
});

promise
.then(function(){
// onFulfilled
}, function(){
// onRejected
throw new Error(oops rejected);
})
.then(void 0, function(e){ // aka .catch
console.log(caught, e);
});

// caught Error: oops rejected

or

var promise = new Promise(function(resolve, reject){
setTimeout(resolve, 1000);
});

promise
.then(function(){
// onFulfilled
throw new Error(oops fulfilled);
}, function(){
// onRejected
})
.then(void 0, function(e){ // aka .catch
console.log(caught, e);
});

// caught Error: oops fulfilled

In both cases, the error will be caught by the first then(), and passed as a rejection to the 2nd (our catch-then).

3. Chaining: Unless your then() callback returns a Promise, it will be immediately resolved.

When you chain promises

promise.then(cb1).then(cb2)…

if cb1 returns a promise, cb2 will wait for it. if cb1 returns any other object (or nothing), it will be immediately resolved.

if cb1 errors out, it will be caught by then() logic, and passed to the onRejected of the 2nd then(). If we don’t specify one, the default onRejected will be used: which just throws an error. Which will be caught again by then(), and passed further down. Example:

var promise = new Promise(function(resolve, reject){
setTimeout(resolve, 1000);
});

promise
.then(function(){
throw new Error(boom);
})
.then(function(){
// we never get here, default onRejected will be called, throw an error, catch it, and pass it further down
})
.then(function(){
// we never get here, default onRejected will be called, throw an error, catch it, and pass it further down
})
.then(function(){
// we never get here, default onRejected will be called, throw an error, catch it, and pass it further down
})
.then(function(){
// we never get here, default onRejected will be called, throw an error, catch it, and pass it further down
})
.then(void 0, function(e){
console.log(caught, e);
});

// caught Error: boom

Be aware this catching mechanic is special to then(), for example this won’t work:

var promise = new Promise(function(resolve, reject){
setTimeout(function(){
throw new Error(wont-work);
}, 1000);
});

promise
.then(void 0, function(e){
console.log(caught, e);
});

// Uncaught Error: wont-work

To illustrate, if we use a custom onRejected, and do not throw an error, we can recover the chain (which may, or may not be, what you want)

var promise = new Promise(function(resolve, reject){
setTimeout(resolve, 1000);
});

promise
.then(function(){
throw new Error(boom);
})
.then(function(){

}, function(e){
console.log(interesting, we caught an error, e); // since we don’t throw a new one, the chain will recover
})
.then(function(){
console.log(recover)
})
.then(void 0, function(e){
console.log(caught, e);
});

// interesting, we caught an error Error: boom
// recover