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');
}
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:
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'))