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 });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 });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) => {
/* [...] */
});Implementing complex asynchronous stuff with callbacks only,
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.
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 */)
;A Promise can only have 3 states
Once it's resolved or rejected, it cannot change state anymore.
Same in real life actually.
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const rand = Math.random();
if (rand > 0.5) {
resolve(rand);
} else {
reject(new Error('random error'));
}
}, 1000);
});You chain with
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 |
Exercise: say some of the functions reject and tell which functions are eventually called and with which arguments
Details: chaining
Promise.all([
fetchUser(),
fetchRestaurants()
])
.then(([user, restaurant]) => {
/* ... */
}); Promise.race([
fetchUser(),
fetchRestaurants()
])
.then(userOrRestaurant => {
/* ... */
}); // @TODO// 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"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>;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