You have your Promise training wheels now, but lets loosen them up a little. What if I told you .then() returns a Promise too, and that new Promise resolves to whatever its callback return s? No ifs about it, it’s the truth! If that was confusing, read it once or twice more, and then check this out:

const g = Promise.resolve(3).then((three) => { return 4; });

What is g ? If you said a Promise, you’d technically be right! We’re calling .then() and I just said that returns a Promise. BUT, what does g resolve to? In English, the code above is putting 3 in a box, then opening that box and passing the contents (3) to the .then , who promptly ignores it and returns 4. .then takes that return value, (4) and wraps it back up again for the outside world. So, g in this case is a box holding 4. Fancy!

It was subtle, but above I mentioned that “ .then returns a promise that resolves to whatever its callback returns”. What does that mean in practice?

// Exactly the same as `g` above!

const h = Promise.resolve(3).then((three) => {

return Promise.resolve(4);

});

Your callback functions can return Promises of their own, and .then / .resolve work their magic to ensure we don’t end up with a Russian doll situation. h here is just a box containing 4, even though our callback returned a box of its own this time. This is where the true power of promises starts to shine. When we have lots of asynchronous things going on at once and we chain them together, we can trust that we will end up with a single box at the end, holding the final result. Promises do all the heavy lifting of keeping our boxes flat, and we can focus on more important work! Beautiful.

// Convoluted!

const i = Promise.resolve(3).then((nope) => {

return Promise.resolve(3).then(four => Promise.resolve(four + 1));

}); // This 'just works'. A single box with a single result.

i.then(x => console.log(x));

// --> 5

There’s Always a Catch

Everything you’ve seen so far has been on the happy path, but Promises have our back even when things go wrong. .resolve and .then are one side of the coin, while .reject and .catch are the other.

You can think of .then and .catch as two lids on the same box, one pulling out good things, and the other pulling out errors. If you .then on a Promise that has an error inside (i.e. has rejected), your .then callback will never be called. The reverse is also true: .catch won’t be called on a Promise that has resolved successfully.

// `.reject` works just like `.resolve`, but with errors!

const j = Promise.reject(new Error('Uh-oh'))

.then(() => console.log('not gonna happen.'))

.then(() => console.log('also not gonna happen.'))

.then(() => console.log('nope.'))

.catch(error => { console.error(error); }); // -> [Error: Uh-oh]

const k = Promise.resolve(3)

.catch(() => console.error('DANGER'))

.catch(() => console.error('something went REALLY wrong'))

.then(contents => console.log(contents)); // -> 3

Since j is rejected, each .then is attempts to open the box, finds nothing, and returns a new rejected Promise. This means it can continue to be chained upon until a .catch opens the box, finds something useful, and calls its handler. k works the same way but in reverse. With no error/rejected promise to .catch , a resolved Promise is returned until we finally hit our .then that logs the contents.

Normally it’s somewhat rare to straight up .reject a Promise. More often than not you’ll end up with a rejected promise when some other handler throws an error.

const l = Promise.resolve(3)

.then((three) => {

throw new Error('Something\'s gone wrong!');

return 4; // Never going to run...

})

.then(() => console.log('Nope! Rejected...'))

.catch(e => console.error(e)); // -> [Error: Something's gone wrong!]

No try/catch or check is required! If you’re inside a .then handler, you can be confident that you’re result is what you expect, while .catch means it’s time for plan B. Promises handle all this for you under the covers!

There’s one more catch with .catch . Just like .then , it returns a Promise that resolves to the return value its handler returns. This means that a .catch handler that runs successfully ‘moves’ the Promise from a rejected state to a resolved state. .then s after a .catch will run until another error is thrown, or a handler returns a rejected Promise.

const m = Promise.resolve(3)

.then(() => console.log('yup'))

.then(() => console.log('also yup'))

.then(() => { throw new Error('Oh no!'); })

.then(() => console.log('nope...'))

.catch(() => console.log('Consider that error HANDLED'))

.catch(() => console.log('nope... we\'re resolved now'))

.then(() => console.log('We\'re back on track now!')); // -> "yup"

// -> "also yup"

// -> "Consider that error HANDLED"

// -> "We\'re back on track now!"

Notice that our .then after the thrown error doesn’t run, because the promise is rejected at that point. Since the following .catch runs without throwing an error, the Promise chain returns to resolved state, and the last .then runs a-ok. It may be a little hard to trace if you’re not used to the flow, but it makes Promises flexible enough to handle just about any situation you can throw at them.

If you wanted to .catch an error, but propagate it along to some other catch handler down the chain, you can always return a rejected Promise.

const n = Promise.resolve(3)

.then(() => console.log('do something'))

.then(() => { throw new Error('whoops'); })

.catch(e => { console.error(e); return Promise.reject(e); })

.catch(e => { console.log('Also Runs!')}); //-> "do something"

//-> [Error: whoops]

//-> "Also Runs!"

All Together

With the powers of .catch and .resolve combined, you now have everything you need to build a Promise of your very own!

const fs = require('fs'); const myLittlePromise = new Promise((resolve, reject) => {

// Resolve if things go ok, or reject if things go awry.

// (Throwing would work too)

fs.readFile('/etc/passwd', (err, data) => {

if (err) {

return reject(err);

}

return resolve(data);

});

});

The Promise constructor takes a handler function that receives both resolve and reject callbacks as arguments. Do whatever work inside the hander you like, and call the appropriate callback when you’re done. In this example we wrap up fs ‘s callback based readFile in a Promise all our own.

Sources/Inspiration/Further Reading

Some of these examples, especially around chaining, are simplified so it’s easier to get the gist of what the final result is. I hand wave over the fact that each step in the chain is a new Promise. This means were you to save those intermediate Promises into their own variables, you could chain off them independently and construct a Promise tree instead of a Promise chain. I don’t see that pattern very often though, probably because it’s a harder to reason about in practice. Nicolás Bevacqua explains all of this very well in his post on Promises. This article is what really made Promises ‘click’ for me, and I highly recommend it.

The whole idea of “Promises as a box” is nothing new either. If you find that mental abstraction particularly fascinating, I’m afraid you might be functionally inclined. For a rocket launch introduction checkout this chapter by Brian Lonsdorf, then loop back through the whole book. Tze-Hsiang Lin’s very informative article is also super helpful. (Don’t worry, there are lots of pictures in both.)