Skip to content

Commit

Permalink
Add an off method to the promise returned from .once() (#100)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
futpib and sindresorhus authored Aug 8, 2022
1 parent bf84337 commit e0b4ba7
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 6 deletions.
11 changes: 9 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ interface Options<EventData> {
debug?: DebugOptions<EventData>;
}

/**
A promise returned from `emittery.once` with an extra `off` method to cancel your subscription.
*/
interface EmitteryOncePromise<T> extends Promise<T> {
off(): void;
}

/**
Emittery is a strictly typed, fully async EventEmitter implementation. Event listeners can be registered with `on` or `once`, and events can be emitted with `emit`.
Expand Down Expand Up @@ -436,7 +443,7 @@ declare class Emittery<
Subscribe to one or more events only once. It will be unsubscribed after the first
event.
@returns The event data when `eventName` is emitted.
@returns The promise of event data when `eventName` is emitted. This promise is extended with an `off` method.
@example
```
Expand All @@ -457,7 +464,7 @@ declare class Emittery<
emitter.emit('🐶', '🍖'); // Nothing happens
```
*/
once<Name extends keyof AllEventData>(eventName: Name | Name[]): Promise<AllEventData[Name]>;
once<Name extends keyof AllEventData>(eventName: Name | Name[]): EmitteryOncePromise<AllEventData[Name]>;

/**
Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
Expand Down
11 changes: 8 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,17 @@ class Emittery {
}

once(eventNames) {
return new Promise(resolve => {
const off = this.on(eventNames, data => {
off();
let off_;

const promise = new Promise(resolve => {
off_ = this.on(eventNames, data => {
off_();
resolve(data);
});
});

promise.off = off_;
return promise;
}

events(eventNames) {
Expand Down
3 changes: 3 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type AnyListener = (eventData?: unknown) => void | Promise<void>;
expectType<PropertyKey | undefined>(eventName);
expectType<AnyListener>(listener);
});
const oncePromise = ee.once('anotherEvent');
oncePromise.off();
await oncePromise;
};
}

Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ const listener = data => console.log(data);

Subscribe to one or more events only once. It will be unsubscribed after the first event.

Returns a promise for the event data when `eventName` is emitted.
Returns a promise for the event data when `eventName` is emitted. This promise is extended with an `off` method.

```js
const Emittery = require('emittery');
Expand Down
24 changes: 24 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,30 @@ test('once() - eventName must be a string, symbol, or number', async t => {
await t.throwsAsync(emitter.once(true), TypeError);
});

test('once() - returns a promise with an unsubscribe method', async t => {
const fixture = '🌈';
const emitter = new Emittery();
const oncePromise = emitter.once('🦄');

const testFailurePromise = Promise.race([
(async () => {
await oncePromise;
t.fail();
})(),
new Promise(resolve => {
setTimeout(() => {
resolve(false);
}, 100);
})
]);

oncePromise.off();
emitter.emit('🦄', fixture);

await testFailurePromise;
t.pass();
});

test.cb('emit() - one event', t => {
t.plan(1);

Expand Down

0 comments on commit e0b4ba7

Please sign in to comment.