Skip to content

Commit 021f863

Browse files
authored
Add signal option (#44)
1 parent 93d16fb commit 021f863

File tree

4 files changed

+81
-0
lines changed

4 files changed

+81
-0
lines changed

index.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ export interface Options<EmittedType extends unknown | unknown[]> {
6363
```
6464
*/
6565
readonly filter?: FilterFunction<EmittedType>;
66+
67+
/**
68+
An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to abort waiting for the event.
69+
*/
70+
readonly signal?: AbortSignal;
6671
}
6772

6873
export interface MultiArgumentsOptions<EmittedType extends unknown[]>

index.js

+12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export function pEventMultiple(emitter, event, options) {
2828
throw new TypeError('The `count` option should be at least 0 or more');
2929
}
3030

31+
options.signal?.throwIfAborted();
32+
3133
// Allow multiple events
3234
const events = [event].flat();
3335

@@ -73,6 +75,10 @@ export function pEventMultiple(emitter, event, options) {
7375
addListener(rejectionEvent, rejectHandler);
7476
}
7577

78+
if (options.signal) {
79+
options.signal.addEventListener('abort', () => rejectHandler(options.signal.reason), {once: true});
80+
}
81+
7682
if (options.resolveImmediately) {
7783
resolve(items);
7884
}
@@ -129,6 +135,8 @@ export function pEventIterator(emitter, event, options) {
129135
throw new TypeError('The `limit` option should be a non-negative integer or Infinity');
130136
}
131137

138+
options.signal?.throwIfAborted();
139+
132140
if (limit === 0) {
133141
// Return an empty async iterator to avoid any further cost
134142
return {
@@ -244,6 +252,10 @@ export function pEventIterator(emitter, event, options) {
244252
addListener(resolutionEvent, resolveHandler);
245253
}
246254

255+
if (options.signal) {
256+
options.signal.addEventListener('abort', () => rejectHandler(options.signal.reason), {once: true});
257+
}
258+
247259
return {
248260
[Symbol.asyncIterator]() {
249261
return this;

readme.md

+6
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ const result = await pEvent(emitter, '🦄', value => value > 3);
133133
// Do something with first 🦄 event with a value greater than 3
134134
```
135135

136+
##### signal
137+
138+
Type: `AbortSignal`
139+
140+
An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to abort waiting for the event.
141+
136142
### pEventMultiple(emitter, event, options)
137143

138144
Wait for multiple event emissions. Returns an array.

test.js

+58
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,36 @@ test('filter option returned with `multiArgs`', async t => {
248248
}), [10_000, '💩']);
249249
});
250250

251+
test('AbortSignal rejects when aborted', async t => {
252+
const emitter = new EventEmitter();
253+
254+
(async () => {
255+
await delay(200);
256+
emitter.emit('🦄', '🌈');
257+
})();
258+
259+
await t.throwsAsync(pEvent(emitter, '🦄', {signal: AbortSignal.timeout(5)}), {
260+
message: 'The operation was aborted due to timeout',
261+
});
262+
t.is(emitter.listenerCount('🦄'), 0);
263+
});
264+
265+
test('AbortSignal that is already aborted rejects immediately', async t => {
266+
const emitter = new EventEmitter();
267+
const controller = new AbortController();
268+
controller.abort(new Error('reason'));
269+
270+
(async () => {
271+
await delay(200);
272+
emitter.emit('🦄', '🌈');
273+
})();
274+
275+
await t.throwsAsync(pEvent(emitter, '🦄', {signal: controller.signal}), {
276+
message: 'reason',
277+
});
278+
t.is(emitter.listenerCount('🦄'), 0);
279+
});
280+
251281
test('event to AsyncIterator', async t => {
252282
const emitter = new EventEmitter();
253283
const iterator = pEventIterator(emitter, '🦄');
@@ -420,6 +450,34 @@ test('resolve event resolves pending promises and finishes the iterator - when f
420450
await t.deepEqual(await iterator.next(), {done: true, value: undefined});
421451
});
422452

453+
test('AsyncIterator - AbortSignal rejects when aborted', async t => {
454+
const emitter = new EventEmitter();
455+
const controller = new AbortController();
456+
const iterator = pEventIterator(emitter, '🦄', {signal: controller.signal});
457+
458+
(async () => {
459+
await delay(200);
460+
emitter.emit('🦄', '🌈');
461+
emitter.emit('🦄', 'Something else.');
462+
await delay(1);
463+
controller.abort(new Error('reason'));
464+
emitter.emit('🦄', 'Some third thing.');
465+
})();
466+
467+
t.deepEqual(await iterator.next(), {done: false, value: '🌈'});
468+
t.deepEqual(await iterator.next(), {done: false, value: 'Something else.'});
469+
await t.throwsAsync(iterator.next(), {message: 'reason'});
470+
t.is(emitter.listenerCount('🦄'), 0);
471+
});
472+
473+
test('AsyncIterator - AbortSignal that is already aborted rejects immediately', t => {
474+
const emitter = new EventEmitter();
475+
const controller = new AbortController();
476+
controller.abort(new Error('reason'));
477+
t.throws(() => pEventIterator(emitter, '🦄', {signal: controller.signal}), {message: 'reason'});
478+
t.is(emitter.listenerCount('🦄'), 0);
479+
});
480+
423481
test('.multiple()', async t => {
424482
const emitter = new EventEmitter();
425483

0 commit comments

Comments
 (0)