Skip to content

Commit 86bde13

Browse files
ylc395sindresorhus
andauthored
Add support for AbortController (#121)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 69193ca commit 86bde13

File tree

5 files changed

+90
-9
lines changed

5 files changed

+90
-9
lines changed

index.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ export default class Emittery<
341341
*/
342342
on<Name extends keyof AllEventData>(
343343
eventName: Name | readonly Name[],
344-
listener: (eventData: AllEventData[Name]) => void | Promise<void>
344+
listener: (eventData: AllEventData[Name]) => void | Promise<void>,
345+
options?: {signal?: AbortSignal}
345346
): UnsubscribeFunction;
346347

347348
/**
@@ -520,7 +521,8 @@ export default class Emittery<
520521
listener: (
521522
eventName: keyof EventData,
522523
eventData: EventData[keyof EventData]
523-
) => void | Promise<void>
524+
) => void | Promise<void>,
525+
options?: {signal?: AbortSignal}
524526
): UnsubscribeFunction;
525527

526528
/**

index.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export default class Emittery {
273273
}
274274
}
275275

276-
on(eventNames, listener) {
276+
on(eventNames, listener, {signal} = {}) {
277277
assertListener(listener);
278278

279279
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
@@ -295,7 +295,18 @@ export default class Emittery {
295295
}
296296
}
297297

298-
return this.off.bind(this, eventNames, listener);
298+
const off = () => {
299+
this.off(eventNames, listener);
300+
signal?.removeEventListener('abort', off);
301+
};
302+
303+
signal?.addEventListener('abort', off, {once: true});
304+
305+
if (signal?.aborted) {
306+
off();
307+
}
308+
309+
return off;
299310
}
300311

301312
off(eventNames, listener) {
@@ -405,14 +416,26 @@ export default class Emittery {
405416
/* eslint-enable no-await-in-loop */
406417
}
407418

408-
onAny(listener) {
419+
onAny(listener, {signal} = {}) {
409420
assertListener(listener);
410421

411422
this.logIfDebugEnabled('subscribeAny', undefined, undefined);
412423

413424
anyMap.get(this).add(listener);
414425
emitMetaEvent(this, listenerAdded, {listener});
415-
return this.offAny.bind(this, listener);
426+
427+
const offAny = () => {
428+
this.offAny(listener);
429+
signal?.removeEventListener('abort', offAny);
430+
};
431+
432+
signal?.addEventListener('abort', offAny, {once: true});
433+
434+
if (signal?.aborted) {
435+
offAny();
436+
}
437+
438+
return offAny;
416439
}
417440

418441
anyEvent() {

index.test-d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
1919
ee.on('anEvent', async () => {});
2020
ee.on('anEvent', data => undefined);
2121
ee.on('anEvent', async data => {});
22+
ee.on('anEvent', async data => {}, {signal: new AbortController().signal});
2223
ee.on(['anEvent', 'anotherEvent'], async data => undefined);
2324
ee.on(Emittery.listenerAdded, ({eventName, listener}) => {
2425
expectType<PropertyKey | undefined>(eventName);
@@ -180,6 +181,8 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
180181
expectType<string | number | undefined>(data);
181182
});
182183

184+
ee.onAny(() => {}, {signal: new AbortController().signal});
185+
183186
const listener = (name: string) => {};
184187
ee.onAny(listener);
185188
ee.offAny(listener);

readme.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ emitter.emit('test');
197197
//=> [subscribe]: test
198198
```
199199

200-
#### on(eventName | eventName[], listener)
200+
#### on(eventName | eventName[], listener, options?: {signal?: AbortSignal})
201201

202202
Subscribe to one or more events.
203203

@@ -222,6 +222,21 @@ emitter.emit('🦄', '🌈'); // log => '🌈' x2
222222
emitter.emit('🐶', '🍖'); // log => '🍖'
223223
```
224224

225+
You can pass an [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to unsubscribe too:
226+
227+
```js
228+
import Emittery from 'emittery';
229+
230+
const abortController = new AbortController();
231+
232+
emitter.on('🐗', data => {
233+
console.log(data);
234+
}, {signal: abortController.signal});
235+
236+
abortController.abort();
237+
emitter.emit('🐗', '🍞'); // nothing happens
238+
```
239+
225240
##### Custom subscribable events
226241

227242
Emittery exports some symbols which represent "meta" events that can be passed to `Emitter.on` and similar methods.
@@ -399,11 +414,11 @@ Same as above, but it waits for each listener to resolve before triggering the n
399414

400415
If any of the listeners throw/reject, the returned promise will be rejected with the error and the remaining listeners will *not* be called.
401416

402-
#### onAny(listener)
417+
#### onAny(listener, options?: {signal?: AbortSignal})
403418

404419
Subscribe to be notified about any event.
405420

406-
Returns a method to unsubscribe.
421+
Returns a method to unsubscribe. Abort signal is respected too.
407422

408423
##### listener(eventName, data)
409424

test/index.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,26 @@ test('on() - isDebug logs output', t => {
205205
t.is(eventStore[0].eventName, 'test');
206206
});
207207

208+
test('on() - use abort signal', async t => {
209+
const emitter = new Emittery();
210+
const abortController = new AbortController();
211+
212+
const calls = [];
213+
const listener = () => {
214+
calls.push(1);
215+
};
216+
217+
emitter.on('abc', listener, {signal: abortController.signal});
218+
219+
await emitter.emit('abc');
220+
t.deepEqual(calls, [1]);
221+
222+
abortController.abort();
223+
await emitter.emit('abc');
224+
225+
t.deepEqual(calls, [1]);
226+
});
227+
208228
test.serial('events()', async t => {
209229
const emitter = new Emittery();
210230
const iterator = emitter.events('🦄');
@@ -769,6 +789,24 @@ test('onAny() - must have a listener', t => {
769789
}, {instanceOf: TypeError});
770790
});
771791

792+
test('onAny() - use abort signal', async t => {
793+
t.plan(4);
794+
795+
const emitter = new Emittery();
796+
const eventFixture = {foo: true};
797+
const abortController = new AbortController();
798+
799+
emitter.onAny((eventName, data) => {
800+
t.is(eventName, '🦄');
801+
t.deepEqual(data, eventFixture);
802+
}, {signal: abortController.signal});
803+
804+
await emitter.emit('🦄', eventFixture);
805+
await emitter.emitSerial('🦄', eventFixture);
806+
abortController.abort();
807+
await emitter.emit('🦄', eventFixture);
808+
});
809+
772810
test.serial('anyEvent()', async t => {
773811
const emitter = new Emittery();
774812
const iterator = emitter.anyEvent();

0 commit comments

Comments
 (0)