JavaScript Promises tl;dr
Tags: javascript, webdev, beginners, architecture
Published: 2021-12-20 10:48:23

A superquick overview of Details about Promises. 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`

```js
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. 
```js
Promise.reject()
  .catch(() => console.log('Catch is called'))
  .finally((s) => console.log('finally called'))
``` 
outputs 

```text
Catch is called
finally is called
```

## Errors inside a promise are forwarded to `.catch`
```js
Promise.resolve()
  .then(() => {})
  .then(() => { throw new Error('hey') })
  .then(() => console.log('i am never called'))
  .catch(() => console.log('error'));
```

## Multiple `.catch` statements are useful
```js
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:

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

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

## `await`ing 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:

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

```js
// outputs ['one', 'two']
Promise.all([Promise.resolve('one'), Promise.resolve('two')])
.then((resultArray) => console.log(resultArray))
```

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