Promises
A short story with promises
const trainFolks = (folks, { min = 1, wait = 2000 }) => {
const waitingForFolks = Promise.delay(wait).tap(() => console.log('doors closed'));
return Promise.filter(folks, folk => Promise.race([
moveIn(folk).return(folk),
waitingForFolks.return()
]))
.tap(logLength)
.tap(rejectIfTooFew(min))
.tap(showExample)
.tap(explainPrinciples)
.map(folk => doExercise(folk)
.tap(() => Math.random() > 0.8 && askQuestion(folk).tap(() => listenAnswer(folk)))
.return(folk)
)
.map(folk => standUp(folk).tap(() => moveOut(folk)).return(folk))
;
};
await trainFolks([1, 2, 3, 4, 5], { min: 3, wait: 3000 });Training story
const trainFolks = async (folks, { min = 1, wait = 2000 }) => {
const waitingForFolks = Promise.delay(wait).tap(() => console.log('doors closed'));
const folks = await Promise.filter(folks, folk => Promise.race([
moveIn(folk).return(folk),
waitingForFolks.return()
]));
logLength(folks);
await rejectIfTooFew(min)(folks);
await showExample(min)(folks);
await explainPrinciples(folks);
await Promise.map(folks, async folk => {
await doExercise(folk);
if (Math.random() > 0.8) {
await askQuestion(folk);
await listenAnswer(folk);
)
});
await Promise.map(folks, folk => standUp(folk).tap(() => moveOut(folk)).return(folk));
return folks;
};
await trainFolks([1, 2, 3, 4, 5], { min: 3, wait: 3000 });Training story
From sync to async code
In synchronous code, you pull values and errors.
You work with them instantly.
In asynchronous code, values and errors are sent to you.
You tell what will happen when received.
try {
const value = getValueSync();
/* [...] */
catch (error) {
/* [...] */
}fetchValueWithCallback(function callback(error, value) => {
/* [...] */
});FROM SYNC TO ASYNC CODE
Implementing complex asynchronous stuff with callbacks only,
- leads to unreadable code
- makes harder to use pure functions
Promise to the rescue
Disclaimer: promises are not the only solution, but it is a widely adopted one.
A Promise is just a wrapper for one value or one error that you don't know yet.
const valuePromise = fetchValueAsPromise();
valuePromise
.then(
value => {
/* [...] */
},
error => {
/* [...] */
}
);It still uses callbacks, but a smarter way.
A promise is a wrapper
It has helpers for building promises from anything
const valuePromise = Promise.resolve(value);
const rejectedPromise = Promise.reject(new Error('message'));It has methods for chaining and transforming the wrapped value
const valuePromise = Promise.resolve(1);
valuePromise // a Promise wrapping 1
.then(n => n * 2) // another Promise wrapping 2
.then(n => n + 1) // another Promise wrapping 3
.then(n => assert(n, 4)) // another Promise which will reject
// cause the assert will throw (3 !== 4)
.catch(error => /* handle error */)
;Promise wrapper advantages
implementations
Any-promise
- Decide from one place which Promise implementation will be used by all your dependency using this same module

Bluebird
- Bluebird is the most used non-native Promise implementation
- It has adds a lot of useful helpers and methods

Promise states
States
A Promise can only have 3 states
- pending
- resolved
- rejected
Once it's resolved or rejected, it cannot change state anymore.
Same in real life actually.
Creating a Promise
With an executor
- JavaScript Promise is a JavaScript constructor
- a Promise is built from an executor function
- the Promise call the executor with a resolve and a reject functions
- the executor calls one of them
- throwing in the executor is the same as calling reject with the error
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const rand = Math.random();
if (rand > 0.5) {
resolve(rand);
} else {
reject(new Error('random error'));
}
}, 1000);
});Chaining logic
Chaining principles
You chain with
- p.then(s, e): s handles p resolution, e handles p rejection
- p.catch(e): e handles p rejection (~p.then(null, e))
Basically, a promise is rejected only if the previous handler throws or rejects.
| Promise.resolve(1) | // p0 | ||||||
| .then | ( | s1 | ) | // p1 | |||
| .catch | ( | e2 | ) | // p2 | |||
| .then | ( | s3 | , | e3 | ) | // p3 | |
| .then | ( | null | , | e4 | ) | // p4 | |
| .then | ( | s5 | , | e5 | ) | // p5 |
Example
- s* and e* variables are functions
- initially, none of them throws or rejects
Exercise: say some of the functions reject and tell which functions are eventually called and with which arguments
Chaining principles
Details: chaining
- means telling what happens on resolution OR rejection
- never replaces the wrapped value
- creates another Promise wrapping the returned value
- creates an eventually rejected Promise ONLY IF the function throws or returns an eventually rejected Promise
Wrapping logic
Wrapping principles
- Wrapping an eventually rejected Promise rejects with its wrapped error
- Wrapping an eventually resolved Promise resolves with its wrapped value
- Wrapping anything else resolves with it
Example
- Promise.resolve(1) resolves with 1
- Promise.resolve(Promise.resolve(2)) resolves with 2
- Promise.resolve(Promise.reject(error)) rejects with error
- Promise.resolve().then(() => 1) resolves with 1
- Promise.resolve().then(() => Promise.resolve(2) resolves with 2
- Promise.resolve().then(() => Promise.reject(error)) rejects with error
Basic concurrency
All
Promise.all([
fetchUser(),
fetchRestaurants()
])
.then(([user, restaurant]) => {
/* ... */
}); Race
Promise.race([
fetchUser(),
fetchRestaurants()
])
.then(userOrRestaurant => {
/* ... */
}); Bluebird
TODO
// @TODOAsync / Await
Thenables
// what does `f1(42)` resolve with ?
const f1 = async n => ({ then: s => s(n*2) });
// how is it similar to `f1` ?
const f2 = async n => await ({ then: s => s(n*2) });
// how does it behave the same as `f1` ?
const f3 = async n => await Promise.resolve(n*2);
// how does it behave the same as `f1` ?
const f4 = async n => Promise.resolve(n*2);// why does it fail ?
const fa = async n => await ({ then: async s => s(await n*2) });
// why does it fail ?
const fb = async n => await ({ then: s => s(await Promise.resolve(n*2)) });
// why does it succeed ?
const fc = async n => await ({ then: async s => s(await Promise.resolve(n*2)) });// who arrives first in both cases?
process.nextTick(() => console.log(0)); f2(42).then(console.log);
process.nextTick(() => console.log(0)); f3(42).then(console.log);
// now you know what "await" does with "thenables"DEFINITION
typescript short Definition
declare class P<T, E> {
constructor(executor: (
resolve: (result: T) => void,
reject: (error: any) => void
) => void);
static resolve<T, E> (result: T | P<T, E>): P<T , E>;
static reject<E> (error: E): P<void, E>;
static all<T, E> (args: (T | P<T, E>)[]): P<T[] , E>;
static race<T, E> (args: (T | P<T, E>)[]): P<T , E>;
then<U, F>(
transform?: (result: T) => U | P<U, F>,
transformError?: (error: E) => U | P<U, F>
): P<U, F>;
catch<U, F>(
transformError?: (error: E) => U | P<U, F>
): P<U, F>;
}Here we write P instead of Promise for convenience only
declare class P<T, E> {
constructor(executor: (
resolve: (result: T) => void,
reject: (error: any) => void
) => void);
static resolve<T, E> (result: W<T, E>): P<T , E>;
static reject<E> (error: E): P<void, E>;
static all<T, E> (args: W<T, E>[]): P<T[] , E>;
static race<T, E> (args: W<T, E>[]): P<T , E>;
then<U, F>(
transform?: (result: T) => W<U, F>,
transformError?: (error: E) => W<U, F>
): P<U, F>;
catch<U, F>(
transformError?: (error: E) => W<U, F>
): P<U, F>;
}
declare type W<T, E> = T | P<T, E>;typescript short Definition
We just extracted the type W<T,E> = either T, or a Promise eventually resolved with T or rejected with E
If you have a doubt, you can report to the strict behavior definitions of promises: https://promisesaplus.com
Exercises
- Promise.all
- queueing
- using Bluebird
- map
- props
- series
- tap/tapCatch
- spread
- get
?
Promises
By Alexis Tondelier
Promises
- 634