JavaScript Promises: Explaining then & catch to a 5 year old.
Tags: javascript, beginners, tutorial, learning
Published: 2024-05-26 01:31:28

## 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:

```js
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

```js
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:

```js
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:

```js
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)

```js
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
```