You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1. add promiseEmitter to preserveOrder: false Promise.race to address case of: infinite concurrency + async iterable producing >1 element
2. use `!isSyncIterator` as shortcut for `isPromiseLike(next)` (`next` is promise iff iterator is async)
3. add `trySpawn` to the `returnValue === pMapSkip && preserveOrder && (promise mapping next input iterable element is pending` branch
4. add tests for changes (1) and (3)
5. tests `rangeAround` helper
6. extra `pMapSkip` tests
7. test for #76
// Treat `promises` as a pool (order doesn't matter)
217
-
: ()=>Promise.race(promises);
238
+
: ()=>Promise.race([
239
+
// Ensures correctness in the case that mappers resolve between the time that one `await nextPromise()` resolves and the next `nextPromise` call is made
240
+
// (these promises would otherwise be lost if an event emitter is not listening - the `promises` pool buffers resolved promises to be processed)
241
+
// (I wonder if it may be actually be possible to convert the `preserveOrder: false` case to _exclusively_ event-based,
242
+
// but such a solution may get messy since we'd want to `yield` from a callback, likely requiring a resolved promises buffer anyway...)
243
+
Promise.race(promises),
244
+
// Ensures correctness in the case that more promises are added to `promises` after the initial `nextPromise` call is made
245
+
// (these additional promises are not be included in the above `Promise.race`)
246
+
// (see comment above `promiseEmitter` declaration for details on when this can occur)
// Swap the fulfilled promise with the last element to avoid an O(n) shift to the `promises` array
@@ -239,7 +272,7 @@ export function pMapIterable(
239
272
letnext;
240
273
try{
241
274
next=iterator.next();
242
-
if(isPromiseLike(next)){
275
+
if(!isSyncIterator){// `!isSyncIterator` iff `isPromiseLike(next)`, but former is already computed
243
276
// Optimization: if our concurrency and/or backpressure is bounded (so that we won't infinitely recurse),
244
277
// and we need to `await` the next `iterator` element, we first eagerly spawn more `mapNext` promises,
245
278
// so that these promises can begin `await`ing their respective `iterator` elements (if needed) and `mapper` results in parallel.
@@ -250,6 +283,7 @@ export function pMapIterable(
250
283
// However, the time needed to `await` and ignore these `done` promises is presumed to be small relative to the time needed to perform common
251
284
// `async` operations like disk reads, network requests, etc.
252
285
// Overall, this can reduce the total time taken to process all elements.
286
+
// Potential TODO: in the `concurrency === Number.POSITIVE_INFINITY` case, we could potentially still optimize here by eagerly spawning some # of promises.
253
287
if(backpressure!==Number.POSITIVE_INFINITY){
254
288
// Spawn if still below concurrency and backpressure limit
255
289
trySpawn();
@@ -291,12 +325,15 @@ export function pMapIterable(
291
325
if(returnValue===pMapSkip){
292
326
// If `preserveOrder: true`, resolve to the next inputIndex's promise, in case we are already being `await`ed
293
327
// NOTE: no chance that `myInputIndex + 1`-spawning code is waiting to be executed in another part of the event loop,
294
-
// but currently `promisesIndexFromInputIndex[myInputIndex + 1] === undefined` (so that we incorrectly `mapNext` and
295
-
// this potentially-currently-awaited promise resolves to the result of mapping a later element than a different member of
296
-
// `promises`, i.e. `promises` resolve out of order), because all `trySpawn`/`mapNext` calls execute the bookkeeping synchronously,
297
-
// before any `await`s.
328
+
// but currently `promisesIndexFromInputIndex[myInputIndex + 1] === undefined` (so that we incorrectly skip this `if` condition and
329
+
// instead call `mapNext`, causing this potentially-currently-awaited promise to resolve to the result of mapping an element
330
+
// of the input iterable that was produced later `myInputIndex + 1`, i.e., no chance `promises` resolve out of order, because:
331
+
// all `trySpawn`/`mapNext` calls execute their bookkeeping synchronously, before any `await`s, so we cannot observe an intermediate
332
+
// state in which input the promise mapping iterable element `myInputIndex + 1` has not been recorded in the `promisesIndexFromInputIndex` ledger.
0 commit comments