diff --git a/index.d.ts b/index.d.ts index de19515..9429799 100644 --- a/index.d.ts +++ b/index.d.ts @@ -132,6 +132,13 @@ interface Options { debug?: DebugOptions; } +/** +A promise returned from `emittery.once` with an extra `off` method to cancel your subscription. +*/ +interface EmitteryOncePromise extends Promise { + 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`. @@ -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 ``` @@ -457,7 +464,7 @@ declare class Emittery< emitter.emit('🐶', '🍖'); // Nothing happens ``` */ - once(eventName: Name | Name[]): Promise; + once(eventName: Name | Name[]): EmitteryOncePromise; /** Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently. diff --git a/index.js b/index.js index 09c2fcc..e8eaabb 100644 --- a/index.js +++ b/index.js @@ -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) { diff --git a/index.test-d.ts b/index.test-d.ts index 8fb41a2..2bd77fa 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -54,6 +54,9 @@ type AnyListener = (eventData?: unknown) => void | Promise; expectType(eventName); expectType(listener); }); + const oncePromise = ee.once('anotherEvent'); + oncePromise.off(); + await oncePromise; }; } diff --git a/readme.md b/readme.md index eba9168..e93700b 100644 --- a/readme.md +++ b/readme.md @@ -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'); diff --git a/test/index.js b/test/index.js index 566c47d..7de7788 100644 --- a/test/index.js +++ b/test/index.js @@ -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);