Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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