Skip to content

Commit

Permalink
Add support for AbortController (#121)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
ylc395 and sindresorhus authored Jan 6, 2025
1 parent 69193ca commit 86bde13
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 9 deletions.
6 changes: 4 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ export default class Emittery<
*/
on<Name extends keyof AllEventData>(
eventName: Name | readonly Name[],
listener: (eventData: AllEventData[Name]) => void | Promise<void>
listener: (eventData: AllEventData[Name]) => void | Promise<void>,
options?: {signal?: AbortSignal}
): UnsubscribeFunction;

/**
Expand Down Expand Up @@ -520,7 +521,8 @@ export default class Emittery<
listener: (
eventName: keyof EventData,
eventData: EventData[keyof EventData]
) => void | Promise<void>
) => void | Promise<void>,
options?: {signal?: AbortSignal}
): UnsubscribeFunction;

/**
Expand Down
31 changes: 27 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export default class Emittery {
}
}

on(eventNames, listener) {
on(eventNames, listener, {signal} = {}) {
assertListener(listener);

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

return this.off.bind(this, eventNames, listener);
const off = () => {
this.off(eventNames, listener);
signal?.removeEventListener('abort', off);
};

signal?.addEventListener('abort', off, {once: true});

if (signal?.aborted) {
off();
}

return off;
}

off(eventNames, listener) {
Expand Down Expand Up @@ -405,14 +416,26 @@ export default class Emittery {
/* eslint-enable no-await-in-loop */
}

onAny(listener) {
onAny(listener, {signal} = {}) {
assertListener(listener);

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

anyMap.get(this).add(listener);
emitMetaEvent(this, listenerAdded, {listener});
return this.offAny.bind(this, listener);

const offAny = () => {
this.offAny(listener);
signal?.removeEventListener('abort', offAny);
};

signal?.addEventListener('abort', offAny, {once: true});

if (signal?.aborted) {
offAny();
}

return offAny;
}

anyEvent() {
Expand Down
3 changes: 3 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
ee.on('anEvent', async () => {});
ee.on('anEvent', data => undefined);
ee.on('anEvent', async data => {});
ee.on('anEvent', async data => {}, {signal: new AbortController().signal});
ee.on(['anEvent', 'anotherEvent'], async data => undefined);
ee.on(Emittery.listenerAdded, ({eventName, listener}) => {
expectType<PropertyKey | undefined>(eventName);
Expand Down Expand Up @@ -180,6 +181,8 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
expectType<string | number | undefined>(data);
});

ee.onAny(() => {}, {signal: new AbortController().signal});

const listener = (name: string) => {};
ee.onAny(listener);
ee.offAny(listener);
Expand Down
21 changes: 18 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ emitter.emit('test');
//=> [subscribe]: test
```

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

Subscribe to one or more events.

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

You can pass an [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to unsubscribe too:

```js
import Emittery from 'emittery';

const abortController = new AbortController();

emitter.on('🐗', data => {
console.log(data);
}, {signal: abortController.signal});

abortController.abort();
emitter.emit('🐗', '🍞'); // nothing happens
```

##### Custom subscribable events

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

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

#### onAny(listener)
#### onAny(listener, options?: {signal?: AbortSignal})

Subscribe to be notified about any event.

Returns a method to unsubscribe.
Returns a method to unsubscribe. Abort signal is respected too.

##### listener(eventName, data)

Expand Down
38 changes: 38 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,26 @@ test('on() - isDebug logs output', t => {
t.is(eventStore[0].eventName, 'test');
});

test('on() - use abort signal', async t => {
const emitter = new Emittery();
const abortController = new AbortController();

const calls = [];
const listener = () => {
calls.push(1);
};

emitter.on('abc', listener, {signal: abortController.signal});

await emitter.emit('abc');
t.deepEqual(calls, [1]);

abortController.abort();
await emitter.emit('abc');

t.deepEqual(calls, [1]);
});

test.serial('events()', async t => {
const emitter = new Emittery();
const iterator = emitter.events('🦄');
Expand Down Expand Up @@ -769,6 +789,24 @@ test('onAny() - must have a listener', t => {
}, {instanceOf: TypeError});
});

test('onAny() - use abort signal', async t => {
t.plan(4);

const emitter = new Emittery();
const eventFixture = {foo: true};
const abortController = new AbortController();

emitter.onAny((eventName, data) => {
t.is(eventName, '🦄');
t.deepEqual(data, eventFixture);
}, {signal: abortController.signal});

await emitter.emit('🦄', eventFixture);
await emitter.emitSerial('🦄', eventFixture);
abortController.abort();
await emitter.emit('🦄', eventFixture);
});

test.serial('anyEvent()', async t => {
const emitter = new Emittery();
const iterator = emitter.anyEvent();
Expand Down

0 comments on commit 86bde13

Please sign in to comment.