Skip to content

Commit 05d8f9b

Browse files
authored
fix(standard-server): batch signal (#353)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved cancellation handling to gracefully manage empty or undefined input scenarios, preventing potential errors. - **Refactor** - Streamlined the internal logic for managing cancellation events, resulting in more robust and predictable behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 57d90ad commit 05d8f9b

File tree

2 files changed

+30
-27
lines changed

2 files changed

+30
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
import { toBatchAbortSignal } from './signal'
22

33
describe('toBatchAbortSignal', () => {
4+
it('should return undefined if contains undefined or empty array', () => {
5+
expect(toBatchAbortSignal([])).toBe(undefined)
6+
expect(toBatchAbortSignal([undefined])).toBe(undefined)
7+
expect(toBatchAbortSignal([AbortSignal.timeout(100), undefined])).toBe(undefined)
8+
expect(toBatchAbortSignal([AbortSignal.timeout(100), undefined, AbortSignal.timeout(100)])).toBe(undefined)
9+
})
10+
411
it('should return a non-aborted signal initially if not all inputs are aborted', () => {
512
const controller1 = new AbortController()
613
const controller2 = new AbortController()
714

8-
// Case 1: Empty array
9-
expect(toBatchAbortSignal([]).aborted).toBe(false)
10-
11-
// Case 2: Only undefined
12-
expect(toBatchAbortSignal([undefined]).aborted).toBe(false)
13-
14-
// Case 3: No signals aborted
15-
expect(toBatchAbortSignal([controller1.signal, controller2.signal]).aborted).toBe(false)
15+
expect(toBatchAbortSignal([controller1.signal, controller2.signal])?.aborted).toBe(false)
1616

17-
// Case 4: Some signals aborted (but not all)
1817
controller1.abort()
19-
expect(toBatchAbortSignal([controller1.signal, controller2.signal]).aborted).toBe(false)
18+
expect(toBatchAbortSignal([controller1.signal, controller2.signal])?.aborted).toBe(false)
2019
})
2120

2221
it('should return an aborted signal initially if all valid inputs are already aborted', () => {
@@ -25,8 +24,8 @@ describe('toBatchAbortSignal', () => {
2524
controller1.abort()
2625
controller2.abort()
2726

28-
const batchSignal = toBatchAbortSignal([controller1.signal, undefined, controller2.signal])
29-
expect(batchSignal.aborted).toBe(true)
27+
const batchSignal = toBatchAbortSignal([controller1.signal, controller2.signal])
28+
expect(batchSignal?.aborted).toBe(true)
3029
})
3130

3231
it('should fire abort event when all signals abort', () => {
@@ -38,22 +37,21 @@ describe('toBatchAbortSignal', () => {
3837

3938
const batchSignal = toBatchAbortSignal([
4039
controllerPreAborted.signal,
41-
undefined,
4240
controllerLater1.signal,
4341
controllerLater2.signal,
4442
])
4543

46-
expect(batchSignal.aborted).toBe(false)
44+
expect(batchSignal?.aborted).toBe(false)
4745

4846
const abortSpy = vi.fn()
49-
batchSignal.addEventListener('abort', abortSpy)
47+
batchSignal?.addEventListener('abort', abortSpy)
5048

5149
controllerLater1.abort()
52-
expect(batchSignal.aborted).toBe(false)
50+
expect(batchSignal?.aborted).toBe(false)
5351
expect(abortSpy).not.toHaveBeenCalled()
5452

5553
controllerLater2.abort()
56-
expect(batchSignal.aborted).toBe(true)
54+
expect(batchSignal?.aborted).toBe(true)
5755
expect(abortSpy).toHaveBeenCalledTimes(1)
5856
})
5957
})

packages/standard-server/src/batch/signal.ts

+15-10
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
export function toBatchAbortSignal(signals: readonly (AbortSignal | undefined)[]): AbortSignal {
1+
export function toBatchAbortSignal(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {
22
const realSignals = signals.filter(signal => signal !== undefined)
33

4-
const controller = new AbortController()
4+
if (realSignals.length === 0 || realSignals.length !== signals.length) {
5+
return undefined
6+
}
57

6-
const abortedSignals = realSignals.filter(signal => signal.aborted)
8+
const controller = new AbortController()
79

8-
if (abortedSignals.length && abortedSignals.length === realSignals.length) {
9-
controller.abort()
10+
const abortIfAllInputsAborted = () => {
11+
if (realSignals.every(signal => signal.aborted)) {
12+
controller.abort()
13+
}
1014
}
1115

16+
abortIfAllInputsAborted()
17+
1218
for (const signal of realSignals) {
1319
signal.addEventListener('abort', () => {
14-
abortedSignals.push(signal)
15-
16-
if (abortedSignals.length === realSignals.length) {
17-
controller.abort()
18-
}
20+
abortIfAllInputsAborted()
21+
}, {
22+
once: true,
23+
signal: controller.signal,
1924
})
2025
}
2126

0 commit comments

Comments
 (0)