Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active March 23, 2022 18:18
Show Gist options
  • Save dfkaye/1260506378b71dac62596acc54174a97 to your computer and use it in GitHub Desktop.
Save dfkaye/1260506378b71dac62596acc54174a97 to your computer and use it in GitHub Desktop.
async iterator function that yields resolved promises from an array but in *resolution* order.
// 17 Feb 2022
// Not mine!!
// It just kicks ass in every way possible!
// Slightly modified and commented version of that by @reu (Rodrigo Navarro)
// https://gist.github.com/reu/d49c9e45138ed6909db5418f20236376
// 20 Feb 2022:
// Change queue semantics (shift, not pop, for FIFO ordering).
// Fix missing result problem (should wait before, not after yield).
// @reu's version produced in response to
// https://twitter.com/yoshuawuyts/status/1494119486429007879
// > Produce a function which takes N Promises, and returns an asyncIterator which yields N resolved values, as soon as they're resolved, not dependent on the order provided.
// We'll call this generator function "Q".
async function * Q(promises = []) {
// Nice lookahead step!
// Visit all awaited promises and add a task that removes the current resolved
// promise from the awaiting array, and pushes the resolved promise value to
// the output queue.
var awaiting = promises.slice();
var queue = [];
for (var promise of awaiting) {
promise.then(result => {
awaiting.splice(awaiting.indexOf(promise), 1);
queue.push(result);
});
}
// Run the while loop, yielding any queued items.
// Loop exits when all awaited promises are resolved.
while (awaiting.length) {
// This turned out to be critical!
// Await next "tick". This pauses the while() loop, giving the awaited
// promises a chance to resolve and call their "then" tasks.
await new Promise(res => setTimeout(res, 0));
// 20 Feb 2022
// Surprising result.
// Do this *after* waiting for next tick, not before;
// Otherwise, not all results are returned!
while (queue.length) {
// 20 Feb 2022
// Pull from the head, not the tail, of the queue.
yield queue.shift();
}
}
}
/* test it out */
// Executor that initializes each promise with a randomized timeout value.
function init(i) {
return (f,r) => {
var t = Math.random(i * 999) * 999;
setTimeout(
() => f(t),
t
);
}
}
// Input
var promises = Array.from({ length: 10 }, (v,i) => new Promise(init(i)));
// Iterator
var it = Q(promises);
// Output
var results = [];
// Exec
(async () => {
for await (var value of it) {
results.push(value);
}
// Render results.
console.table(results);
// Assert all promises were resolved.
console.assert(
results.length === promises.length,
`Should resolve all promises, but resolved ${results.length} out of ${results.length}.`
);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment