Skip to content

Commit 8904ea9

Browse files
committed
Fix: after first error, future .next() rethrows and no further input is pulled
1 parent 3e45685 commit 8904ea9

3 files changed

Lines changed: 52 additions & 7 deletions

File tree

README.MD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Async Parallel Pipe
77
[![Coverage Status](https://coveralls.io/repos/github/snatalenko/async-parallel-pipe/badge.svg?branch=main)](https://coveralls.io/github/snatalenko/async-parallel-pipe?branch=main)
88
[![NPM Downloads](https://img.shields.io/npm/dm/async-parallel-pipe.svg)](https://www.npmjs.com/package/async-parallel-pipe)
99

10-
The function pulls input values from async iterable input, runs async action, and returns async iterable output in a same order as input was received.
10+
The function pulls input values from an iterable or async iterable input, runs an async action on each yielded value with a configurable concurrency limit, and returns an async iterable output in the same order as input was received. The final return value of the input iterator (if any) is forwarded as the terminal `{ done: true, value }` of the output without applying the action.
1111

1212
Here is an example that requests random numbers from random.org with not more than 2 concurrent requests at a time:
1313

src/parallelPipe.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function parallelPipe<TInput, TOutput>(
1818
const runningTasks: Array<Promise<IteratorResult<TOutput>> | IteratorResult<TOutput>> = [];
1919
let inputDone = false;
2020
let inputIndex = 0;
21+
let iteratorFailure: unknown;
2122

2223
async function executeActionOnIterableInput(input: IteratorResult<TInput, any>): Promise<
2324
{ done: false, value: TOutput } |
@@ -53,20 +54,30 @@ export function parallelPipe<TInput, TOutput>(
5354

5455
return {
5556
async next() {
57+
if (iteratorFailure)
58+
throw iteratorFailure;
59+
5660
// start processing input once output is requested
5761
if (!runningTasks.length)
5862
pullFromInput();
5963

60-
if (!runningTasks.length)
64+
const firstRunningTask = runningTasks.shift();
65+
if (!firstRunningTask)
6166
return { done: true, value: undefined };
6267

63-
const firstRunningTask = runningTasks.splice(0, 1)[0];
64-
const { done, value } = await firstRunningTask;
68+
try {
69+
const { done, value } = await firstRunningTask;
6570

66-
if (!done)
67-
pullFromInput();
71+
if (!done)
72+
pullFromInput();
6873

69-
return { done, value };
74+
return { done, value };
75+
}
76+
catch (err: unknown) {
77+
iteratorFailure = err; // switch to failed state
78+
inputDone = true; // stop pulling new tasks
79+
throw err;
80+
}
7081
},
7182
[Symbol.asyncIterator]() {
7283
return this;

tests/unit/parallelPipe.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,40 @@ describe('parallelPipe', () => {
8181
expect(caughtError.message).toEqual('fail');
8282
});
8383

84+
it('does not consume remaining input after an error', async () => {
85+
const inputValues = [1, 2, 3, 4, 5];
86+
let nextCalls = 0;
87+
const input: AsyncIterableIterator<number> = {
88+
next: async () => {
89+
nextCalls++;
90+
const value = inputValues[nextCalls - 1];
91+
return value === undefined ? { done: true, value: undefined } : { done: false, value };
92+
},
93+
[Symbol.asyncIterator]() {
94+
return this;
95+
}
96+
};
97+
98+
const action = async (x: number) => {
99+
if (x === 2)
100+
throw new Error('fail-2');
101+
102+
return x;
103+
};
104+
105+
const iterator = parallelPipe(input, 2, action);
106+
107+
const res1 = await iterator.next();
108+
expect(res1).toEqual({ value: 1, done: false });
109+
110+
await expect(iterator.next()).rejects.toThrow('fail-2');
111+
112+
// First `next()` fills the queue with `limit` items, then pulls one more after yielding.
113+
// After the first rejection, no additional input should be consumed.
114+
await Promise.resolve();
115+
expect(nextCalls).toBe(3);
116+
});
117+
84118
it('does not exceed the specified parallel limit', async () => {
85119

86120
const inputValues = [1, 2, 3, 4, 5];

0 commit comments

Comments
 (0)