DEV Community

Hello Web!
Hello Web!

Posted on

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)