Although I believe we often today navigate an asynchronous environment by forcing it to behave synchronously - kind of like stating “the worse for the facts” when a theory is proved flawed - it’s valuable to be able to create sequentially called (potentially) asynchronous functions.
As a step in understanding this process better, I’ve played with different ways of solving this problem. I wanted a logic handling latency between nodes in a sequence of function calls, a few synchronous (normalization, etc.), and some asynchronous (HTTP, DB requests, etc.). I also wanted to resolve a situation in which some steps require previous ones.
I made two variants of the same function. One allowing you the chain functions, and an implementation of ‘compose’ - both permitting asynchronous calls.
This my expected output, aiming to in mock something close-enough a real scenario.
[ 1, 2, 3 ] → initial value
[ 2, 4, 6 ] → doubled
[ 3, 5, 7 ] → 2s later, increased by one
[ 4, 6, 8 ] → 2s later, increased by one
[ 8, 12, 16 ] → doubled
Result: [9,13,17] → 2s later, increased by one
const inc = (x) => x + 1;
const double = (x) => x + x;
const doubleList = (xs) => xs.map(double);
const fakeRequest = (xs) =>
new Promise((resolve) => setTimeout(() => resolve(xs.map(inc)), 2000));
class SequentialPromises {
constructor(x) {
this.x = x;
}
async then(fn) {
const v = await fn(this.x);
return this.x === undefined ? v : new SequentialPromises(v);
}
}
const trace = (fn) => (x) => {
console.log(x);
return fn(x);
};
const seqChain = async (initialValue) => {
const result = await new SequentialPromises(initialValue)
.then(trace(doubleList))
.then(trace(fakeRequest))
.then(trace(fakeRequest))
.then(trace(doubleList))
.then(trace(fakeRequest));
console.log(`Result: [${result}]`);
};
seqChain([1, 2, 3]);
const compose = (...fns) => async (v) => {
for (let i = fns.length - 1; i >= 0; i--) {
v = await fns[i](v);
}
return v;
};
const asyncCompose = async (initialValue) => {
const result = await compose(
trace(fakeRequest),
trace(doubleList),
trace(fakeRequest),
trace(fakeRequest),
trace(doubleList)
)(initialValue);
console.log(`Result: [${result}]`);
};
asyncCompose([1, 2, 3]);