David Lorenz
Food for Developers

Food for Developers

JavaScript Promises tl;dr

JavaScript Promises tl;dr

A superquick overview of Details about Promises

David Lorenz's photo
David Lorenz
·Dec 20, 2021·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

  • Promises always chain
  • finally
  • Errors inside a promise are forwarded to .catch
  • Multiple .catch statements are useful
  • async functions are Promise Wrappers
  • awaiting promises must be done carefully
  • Promise.race and Promise.any
  • Promise.all

Stop the talk, let's start getting into it.

Promises always chain

If then or catch return a value that is NOT a promise then it will be wrapped into a new promise and chained and forwarded to the next one. That means starting from a catch you can return a value and .then it.

All of the samples here will output Hello World1

const appendWorld = s => `${s} World`;
const appendOne = s => `${s}1`;
const log = v => console.log(v);

Promise.resolve('Hello').then(appendWorld).then(appendOne).then(log);
Promise.resolve('Hello').then(v => Promise.resolve(appendWorld(v))).then(appendOne).then(log);
Promise.reject('Hello').catch(appendWorld).then(appendOne).then(log);
Promise.resolve('Blogging').then(() => 'Hello').then(appendWorld).then(appendOne).then(log)

finally

finally cannot return a value that can be chained. Kind of implied by it's name. It is called no matter if another .then or .catch was called before. When the Promise was fulfilled in any way then .finally is called. Good for cleanup work.

E.g.

Promise.reject()
  .catch(() => console.log('Catch is called'))
  .finally((s) => console.log('finally called'))

outputs

Catch is called
finally is called

Errors inside a promise are forwarded to .catch

Promise.resolve()
  .then(() => {})
  .then(() => { throw new Error('hey') })
  .then(() => console.log('i am never called'))
  .catch(() => console.log('error'));

Multiple .catch statements are useful

Promise.resolve()
  .then(() => Promise.reject())
  .catch(() => console.log('much rejection'))
  .then(() => console.log('i can continue doing stuff'))
  .then(() => Promise.reject('another one'))
  .catch(() => console.log('catching the second chain'))

async functions are Promise Wrappers

The following code statements have the same effect:

// async
async function foobar() {
  return 'foo';
}

// non-async
function foobar() {
  return Promise.resolve('foo');
}

awaiting promises must be done carefully

If you await a promise then you need to be careful when checking for "success" because errors can be hidden.

See the following example:

const foobar = await Promise.reject(new Error('error thrown')).catch(error => error);

if (foobar) {
  // This does not imply success ⚠️👩‍🚀
} else {
 // This does not imply an error case
}

The problem is that the provided promise is properly caught. Referring back to promise-chaining now the result of the catch statement can be chained, hence new Error... is the resulting object if you'd call .then on it. And that is simply the same as calling await on it. So here foobar contains new Error... which is an object which when checking for if(foobar) returns true although an error was thrown. So you need to be aware of what your promises return.

Promise.race and Promise.any

Both race and any complete with the Promise whichever is first. But there is a big difference: race finishes with the first Promise to EITHER resolve OR reject whilst any finishes only with the first actually resolved Promise.

In this Promise.race sample the error Promise wins because it is first:

const promise1 = new Promise((resolve, reject) => setTimeout(reject, 100));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 300));
Promise
  .race([promise1, promise2])
  .then(v => console.log('resolved', v))
  .catch(v => console.log('error', v));

In this Promise.any sample the resolved Promise wins because it is the first to actually resolve:

const promise1 = new Promise((resolve, reject) => setTimeout(reject, 100));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 300));
Promise
  .any([promise1, promise2])
  .then(v => console.log('resolved', v))
  .catch(v => console.log('error', v));

Promise.all

This one is pretty intuitive: It either resolves when ALL promises are resolved OR it rejects when one of the promises is rejected.

// outputs ['one', 'two']
Promise.all([Promise.resolve('one'), Promise.resolve('two')])
.then((resultArray) => console.log(resultArray))
// outputs 'error'
Promise.all([Promise.resolve('one'), Promise.resolve('two'), Promise.reject()])
.then((resultArray) => console.log(resultArray))
.catch(() => console.log('error'))
 
Share this