Composing asynchronous calls

2021-05-13

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

A ‘thenable’ solution

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]);


…using compose

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]);

About | Archive