UNPKG

fast-merge-async-iterators

Version:
81 lines 3.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); async function* merge(...args) { const mode = typeof args[0] === "string" ? args.shift() : "iters-close-nowait"; const iters = args; const promises = new Map(iters .map((iter) => Symbol.asyncIterator in iter ? iter[Symbol.asyncIterator]() : iter) .map((iterator) => [iterator, next(iterator)])); try { while (promises.size > 0) { const reply = await Promise.race(promises.values()); if (reply.length === 3) { const [, iterator, err] = reply; // Since this iterator threw, it's already ended, so we remove it. promises.delete(iterator); throw err; } const [res, iterator] = reply; if (res.done) { promises.delete(iterator); } else { // This allows the consumer of the value to delete it on its end, and // the value will be then garbage collected. Works only for the cases // when the iterators fed to merge() are plain (not async generators // with yield; in the latter case, you will have to add this // process.nextTick() cleanup there too). process.nextTick(() => { res.value = null; }); // Return the value to the consumer. In the next tick, it will be // removed from here, so even if nobody calls .next() on the merged // iterator anymore, the value will be garbage collected. yield res.value; // Iterators starvation prevention. Imagine you merge two iterators, and // iterator1 always yields something, whilst iterator2 yields rarely. If // we don't delete and then re-add the Promise in the end, then // Promise.race() would have always returned values from iterator1 and // never from iterator2. With deletion and re-adding to the end, we tell // Promise.race() to give a fair chance to all iterators. promises.delete(iterator); // After we're back from yield (aka someone called .next() on the merged // iterator again), schedule the next value fetching from the same // iterator and re-add the Promise to the END of the set, subject for // Promise.race() fair pickup. promises.set(iterator, next(iterator)); } } } finally { switch (mode) { case "iters-noclose": // Let inner iterables continue running in nowhere until they reach // the next yield and block on it, then garbage collected (since // no-one will read the result of those yields). break; case "iters-close-nowait": promises.forEach((_, iterator) => { var _a; return void ((_a = iterator.return) === null || _a === void 0 ? void 0 : _a.call(iterator)); }); break; case "iters-close-wait": await Promise.all([...promises.keys()].map((iterator) => { var _a; return (_a = iterator.return) === null || _a === void 0 ? void 0 : _a.call(iterator); })); (await Promise.all(promises.values())).forEach((reply) => { if (reply.length === 3) { const [, , err] = reply; throw err; } }); break; } } } exports.default = merge; async function next(iterator) { return iterator .next() .then((res) => [res, iterator]) .catch((err) => [undefined, iterator, err]); } //# sourceMappingURL=index.js.map