Loïc TRUCHOT
JavaScript Fullstack Expert & Senior web developer
Welcome to
TZ 2021 - Loïc TRUCHOT
const randomTime = () => Math.random() * 100;
// imperative version, leads to uncertain results
let val = "";
setTimeout(() => val += "it begin. ", randomTime());
setTimeout(() => val += "it continue. ", randomTime());
setTimeout(() => val += "it end. ", randomTime());
setTimeout(() => console.log(val), randomTime());
imperative approach
Console verdict: yuk!
AKA bugland
let val = "";
setTimeout(() => {
val += "it begin. ";
setTimeout(() => {
val += "it continue. ";
setTimeout(() => {
val += "it end. ";
setTimeout(() => {
console.log(val),
randomTime()
});
}, randomTime());
}, randomTime());
}, randomTime());
const start = (val: string, callback: Function) => {
setTimeout(() => callback(val + "it begin. ", addEnd), randomTime())
};
const addContinue = (val: string, callback: Function) => {
setTimeout(() => callback(val + "it continue. ", console.log), randomTime());
}
const addEnd = (val: string, callback: Function) => {
setTimeout(() => callback(val + "it end. "), randomTime());
}
start("", addContinue);
Console verdict: at least it works
1. Pyramid of Doom
2. Spaghetti code
welcome to callback hell
Let's try to re-discover Promises (~500l)
const double = (x: number) => x * 2;
const inc = (x: number) => x + 1;
console.log(inc(double(double(1)))); // not flat
const applyTo = (val: any, f: UnaryFn) => f(val);
const pipe = (...fns: UnaryFn[]) => (val: any) => fns.reduce(applyTo, val);
pipe(double, double, double, console.log)(1); // more flat than earth
1.Compose approach
2. Pipe approach
the art of flatten
const compose = (f: UnaryFn, g: UnaryFn) => (a: any) => f(g(a));
const quadruple = compose(double, double);
console.log(quadruple(1));// a little flatter
const composeN = (...fns: UnaryFn[]) => (val: any) => fns
.reverse()
.reduce((acc: any, cur: UnaryFn) => cur(acc), val);
const quadrupleThenIncThenLog = composeN(console.log, inc, double, double);
quadrupleThenIncThenLog(1); // very flat
export type UnaryFn = (a: any) => any;
const applyTo = (val: any, f: UnaryFn) => f(val);
const pipe = (...fns: UnaryFn[]) => (val: any) => fns.reduce(applyTo, val);
class NaiveSyncPromise {
private _callbacks: UnaryFn[] = [];
constructor(firstValue: any) {
setTimeout(() => pipe(...this._callbacks)(firstValue));
}
then(callback: UnaryFn) {
this._callbacks.push(callback);
return this;
}
}
const naiveSyncPromise = new NaiveSyncPromise("");
naiveSyncPromise
.then((value) => value + "it begin. ")
.then((value) => value + "it continue. ")
.then((value) => value + "it end.")
.then(console.log);
A wrapper to compose
type NextPromise = (val: any) => SyncPromise;
class SyncPromise {
constructor(public value: any) {}
then(next: NextPromise) {
setTimeout(() => {
this.value = next(this.value).value;
});
return this;
}
map(f: UnaryFn) {
return this.then((value) => new SyncPromise(f(value)));
}
}
const syncPromise = new SyncPromise("");
syncPromise
.then((value) => new SyncPromise(value + "it begin. "))
.then((value) => new SyncPromise(value + "it continue. "))
.map((value) => value + "it end.")
.then((value) => { // tap ?
console.log(value);
return new SyncPromise(value);
});
A wrapper to chain... and free monadic stuff !
type NextPromise = (val: any) => NaiveAsyncPromise;
class NaiveAsyncPromise {
private _callbacks: NextPromise[] = [];
constructor(private handle: Handler) {
setTimeout(() => {
if (this._callbacks.length) {
this._callbacks.reduce((acc: NaiveAsyncPromise, cur: NextPromise) => {
const nextPromise = new NaiveAsyncPromise(acc.resolve(cur));
nextPromise.handle(identity);
return nextPromise;
}, this);
}
});
}
private resolve(next: NextPromise) {
return (resolve: any) =>
this.handle(
(result: any) => next(result).handle((x: any) => resolve(x)),
);
}
then(next: NextPromise) {
this._callbacks.push(next);
return this;
}
}
the convoluted implementation
const naiveAsyncPromise = new NaiveAsyncPromise((resolve) => resolve(""));
naiveAsyncPromise
.then((val: any) =>
new NaiveAsyncPromise(
(resolve) => setTimeout(() => resolve(val + "it begin. "), randomTime()),
)
)
.then((val: any) =>
new NaiveAsyncPromise(
(resolve) =>
setTimeout(() => resolve(val + "it continue. "), randomTime()),
)
)
.then((val: any) =>
new NaiveAsyncPromise(
(resolve) => setTimeout(() => resolve(val + "it end. "), randomTime()),
)
)
.then((val: any) =>
new NaiveAsyncPromise(
(_) => setTimeout(() => console.log(val), randomTime()),
)
);
the crazy chain
a design pattern to enlighten chaining
type NextPromise = (val: any) => FantasyPromise;
class FantasyPromise {
constructor(public handle: Handler) {}
then(next: NextPromise) {
return new FantasyPromise(
(resolve: Function) => this.handle((a: any) => next(a).handle(resolve)),
);
}
resolve() {
return this.handle(identity);
}
}
So fanciful...
const fantasyPromise = new FantasyPromise((resolve: any) => resolve(""));
fantasyPromise
.then((s) =>
new FantasyPromise(
(resolve: any) =>
setTimeout(() => resolve(s + "it begin. "), randomTime()),
)
)
.then((s) =>
new FantasyPromise(
(resolve: any) =>
setTimeout(() => resolve(s + "it continue. "), randomTime()),
)
)
.then((s) =>
new FantasyPromise(
(resolve: any) => setTimeout(() => resolve(s + "it end. "), randomTime()),
)
)
.then((s) => new FantasyPromise((_: any) => console.log(s)))
.resolve();
...And this is almost nothing
type NextPromise = (val: any) => MonadicPromise;
class MonadicPromise {
constructor(public fork: Handler) {}
static of(x: any) {
return new MonadicPromise((resolve) => resolve(x));
}
chain(next: NextPromise) {
return new MonadicPromise(
(resolve: Function) =>
this.fork(
(a: Handler) => next(a).fork(resolve),
),
);
}
map(f: Function) {
return new MonadicPromise((resolve) =>
this.fork((a: Handler) => resolve(f(a)))
);
}
extract() {
return this.fork(identity);
}
}
const nextStr = (s: string) =>
(prev: string) =>
new MonadicPromise(
(resolve: any) =>
setTimeout(
() => resolve(prev + s),
randomTime(),
),
);
const monadicPromise = MonadicPromise.of("");
monadicPromise
.chain(nextStr("it begin. "))
.chain(nextStr("it continue. "))
.chain(nextStr("it end."))
.map(console.log)
.extract();
after, all, alt, and, ap, attempt-p, attempt, bichain, bimap, both, cache, chain-rej, chain, coalesce, done, encase-p, encase, extract-left, extract-right, fork-catch, fork, future, go, hook, lastly, map-rej, map, node, pap, par, parallel, promise, race, reject-after, seq, swap, value...
Text
class Maybe {
static of(x: any) {
return new Maybe(x);
}
constructor(private _value: any) {}
isNothing() {
return (this._value === null);
}
map(f: Function) {
return this.isNothing() ?
Maybe.of(null) :
new Maybe(f(this._value));
}
extract() {
return this._value;
}
chain(f: (a: any) => Maybe) {
return this.map(f).extract();
}
orElse(value: any) {
if (this.isNothing()) {
return Maybe.of(value);
}
return this;
}
}
const document = {
getElementById: (s: string) => s === "always"
? s
: null,
createElement: (s: string) => s,
body: {
appendChild: (s: string) => s,
},
};
Maybe
.of(document.getElementById("never"))
.orElse(document.createElement("div"))
.chain((el: any) => {
const name = document.body.appendChild(el);
return new Maybe(name);
})
.map(console.log);
even OO people likes monads
so much stuff...
Can you tell me
By Loïc TRUCHOT