Sequential Promise.all

Actual issue

When you have an array of Promise and needs to execute them sequentially, you have to implement a custom function.

Actual soluce

function getRandomInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}

function foo() {
  return new Promise((resolve) => {
    const time = getRandomInt(500);

    setTimeout(() => resolve(time), time);
  });
}

async function promiseAllSeqRecursive(arr, callback, rets = [], i = 0) {
  if (i >= arr.length) {
    callback(rets);

    return;
  }

  rets.push(await arr[i]);

  promiseAllSeqRecursive(arr, callback, rets, i + 1);
}

function promiseAllSeq(arr) {
  return new Promise((resolve) => {
    promiseAllSeqRecursive(arr, resolve);
  });
}

(async() => {
  const rets = await promiseAllSeq([
    foo(),
    foo(),
    foo(),
    foo(),
  ]);

  console.log(rets);
})();

Proposal

const rets = Promise.allSeq([
   p1(),
   p2(),
   p3(),
   p4(),
]);

Do you mean:


const promises = [p1, p2, p3, p4];

promises.reduce((prev, task) => prev.then(task), Promise.resolve());

/* or */

for (const task of promises) {

await task();

}

?

Yes also. The point is that there is a standard method to resolve all the promises concurrently, it could be a standard method in order to resolve them sequentially too.

Promises are not executed. Only functions can be.

When you already have an array of promises, that means the tasks whose results the promises represent are already running. In your example code, the setTimeouts are all invoked at the same time, there is nothing that could make them sequential afterwards.

To achieve sequential execution, you really need to write

(async() => {
  const rets = [
    await foo(),
    await foo(),
    await foo(),
    await foo(),
  ];
  console.log(rets);
})();

No helper function required for that. Similarly, if you had an array of promise-returning functions (which does not parallel Promise.all at all), you could just write a loop:

(async() => {
  const fns = [foo, foo, foo, foo];
  const rets = [];
  for (const fn of fns) rets.push(await fn());
  console.log(rets);
})();

We hardly need a helper function for that in the standard library.

2 Likes

You are right my choice of words was indeed very bad (wrong). It's not for executing the promises sequentially, but resolving them sequentially.

for of is a good example, but it makes me think of Array.forEach and Array.reduce :

let v = 0;

[10, 1, 27].forEach((x) => {
      v += x;
});

Compared to

const v = [10, 1, 27].reduce((tmp, x) => tmp + x, 0);

When you have a starter value that adapt depending on a array iteration, reduce is more appropriate. Promise.allSeq could be in the same spirit. Having standard library helpers to simplify trivial needs.

I can simplify your promiseAllSeq a lot using for ... of:

async function promiseAllSeq(promises) {
    let results = []
    for (const p of promises) results.push(await p)
    return results
}

This IMHO is pretty rare to need, and 99% of my needs have really called for something like this:

async function forEachAsync(coll, func) {
    for (const value of coll) await func(value)
}

// Alternatively, using `Array.prototype.reduce`
function forEachAsync(coll, func) {
    return [...coll].reduce(
        (p, value) => p.then(() => func()),
        Promise.resolve()
    )
}

And for the rare cases where I would actually need something like that, I can just use something like this instead:

;(async () => {
    for (const value of list) await doSomething()
})()
.then(
    () => returnSuccess(),
    e => returnError(e)
)

Note that engines can optimize async functions far better than they can reduce loops (they don't need to do a full function call), and also, that reduce-based implementation commonly suffers from a nice little memory leak thanks to a common implementation bug. (I just filed this V8 bug in an attempt to get it resolved for at least one major implementation.)