emittery 1.0.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.d.ts +18 -6
  2. package/index.js +45 -12
  3. package/package.json +1 -1
  4. package/readme.md +29 -6
package/index.d.ts CHANGED
@@ -341,7 +341,8 @@ export default class Emittery<
341
341
  */
342
342
  on<Name extends keyof AllEventData>(
343
343
  eventName: Name | readonly Name[],
344
- listener: (eventData: AllEventData[Name]) => void | Promise<void>
344
+ listener: (eventData: AllEventData[Name]) => void | Promise<void>,
345
+ options?: {signal?: AbortSignal}
345
346
  ): UnsubscribeFunction;
346
347
 
347
348
  /**
@@ -461,10 +462,12 @@ export default class Emittery<
461
462
  ): void;
462
463
 
463
464
  /**
464
- Subscribe to one or more events only once. It will be unsubscribed after the first
465
- event.
465
+ Subscribe to one or more events only once. It will be unsubscribed after the first event that matches the predicate (if provided).
466
+
467
+ @param eventName - The event name(s) to subscribe to.
468
+ @param predicate - Optional predicate function to filter event data. The event will only be emitted if the predicate returns true.
466
469
 
467
- @returns The promise of event data when `eventName` is emitted. This promise is extended with an `off` method.
470
+ @returns The promise of event data when `eventName` is emitted and predicate matches (if provided). This promise is extended with an `off` method.
468
471
 
469
472
  @example
470
473
  ```
@@ -481,11 +484,19 @@ export default class Emittery<
481
484
  console.log(data);
482
485
  });
483
486
 
487
+ // With predicate
488
+ emitter.once('data', data => data.ok === true).then(data => {
489
+ console.log(data);
490
+ //=> {ok: true, value: 42}
491
+ });
492
+
484
493
  emitter.emit('🦄', '🌈'); // Logs `🌈` twice
485
494
  emitter.emit('🐶', '🍖'); // Nothing happens
495
+ emitter.emit('data', {ok: false}); // Nothing happens
496
+ emitter.emit('data', {ok: true, value: 42}); // Logs {ok: true, value: 42}
486
497
  ```
487
498
  */
488
- once<Name extends keyof AllEventData>(eventName: Name | readonly Name[]): EmitteryOncePromise<AllEventData[Name]>;
499
+ once<Name extends keyof AllEventData>(eventName: Name | readonly Name[], predicate?: (eventData: AllEventData[Name]) => boolean): EmitteryOncePromise<AllEventData[Name]>;
489
500
 
490
501
  /**
491
502
  Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
@@ -520,7 +531,8 @@ export default class Emittery<
520
531
  listener: (
521
532
  eventName: keyof EventData,
522
533
  eventData: EventData[keyof EventData]
523
- ) => void | Promise<void>
534
+ ) => void | Promise<void>,
535
+ options?: {signal?: AbortSignal}
524
536
  ): UnsubscribeFunction;
525
537
 
526
538
  /**
package/index.js CHANGED
@@ -165,13 +165,15 @@ function defaultMethodNamesOrAssert(methodNames) {
165
165
  const isMetaEvent = eventName => eventName === listenerAdded || eventName === listenerRemoved;
166
166
 
167
167
  function emitMetaEvent(emitter, eventName, eventData) {
168
- if (isMetaEvent(eventName)) {
169
- try {
170
- canEmitMetaEvents = true;
171
- emitter.emit(eventName, eventData);
172
- } finally {
173
- canEmitMetaEvents = false;
174
- }
168
+ if (!isMetaEvent(eventName)) {
169
+ return;
170
+ }
171
+
172
+ try {
173
+ canEmitMetaEvents = true;
174
+ emitter.emit(eventName, eventData);
175
+ } finally {
176
+ canEmitMetaEvents = false;
175
177
  }
176
178
  }
177
179
 
@@ -273,7 +275,7 @@ export default class Emittery {
273
275
  }
274
276
  }
275
277
 
276
- on(eventNames, listener) {
278
+ on(eventNames, listener, {signal} = {}) {
277
279
  assertListener(listener);
278
280
 
279
281
  eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
@@ -295,7 +297,18 @@ export default class Emittery {
295
297
  }
296
298
  }
297
299
 
298
- return this.off.bind(this, eventNames, listener);
300
+ const off = () => {
301
+ this.off(eventNames, listener);
302
+ signal?.removeEventListener('abort', off);
303
+ };
304
+
305
+ signal?.addEventListener('abort', off, {once: true});
306
+
307
+ if (signal?.aborted) {
308
+ off();
309
+ }
310
+
311
+ return off;
299
312
  }
300
313
 
301
314
  off(eventNames, listener) {
@@ -321,11 +334,19 @@ export default class Emittery {
321
334
  }
322
335
  }
323
336
 
324
- once(eventNames) {
337
+ once(eventNames, predicate) {
338
+ if (predicate !== undefined && typeof predicate !== 'function') {
339
+ throw new TypeError('predicate must be a function');
340
+ }
341
+
325
342
  let off_;
326
343
 
327
344
  const promise = new Promise(resolve => {
328
345
  off_ = this.on(eventNames, data => {
346
+ if (predicate && !predicate(data)) {
347
+ return;
348
+ }
349
+
329
350
  off_();
330
351
  resolve(data);
331
352
  });
@@ -405,14 +426,26 @@ export default class Emittery {
405
426
  /* eslint-enable no-await-in-loop */
406
427
  }
407
428
 
408
- onAny(listener) {
429
+ onAny(listener, {signal} = {}) {
409
430
  assertListener(listener);
410
431
 
411
432
  this.logIfDebugEnabled('subscribeAny', undefined, undefined);
412
433
 
413
434
  anyMap.get(this).add(listener);
414
435
  emitMetaEvent(this, listenerAdded, {listener});
415
- return this.offAny.bind(this, listener);
436
+
437
+ const offAny = () => {
438
+ this.offAny(listener);
439
+ signal?.removeEventListener('abort', offAny);
440
+ };
441
+
442
+ signal?.addEventListener('abort', offAny, {once: true});
443
+
444
+ if (signal?.aborted) {
445
+ offAny();
446
+ }
447
+
448
+ return offAny;
416
449
  }
417
450
 
418
451
  anyEvent() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emittery",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "Simple and modern async event emitter",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/emittery",
package/readme.md CHANGED
@@ -197,7 +197,7 @@ emitter.emit('test');
197
197
  //=> [subscribe]: test
198
198
  ```
199
199
 
200
- #### on(eventName | eventName[], listener)
200
+ #### on(eventName | eventName[], listener, options?: {signal?: AbortSignal})
201
201
 
202
202
  Subscribe to one or more events.
203
203
 
@@ -222,6 +222,21 @@ emitter.emit('🦄', '🌈'); // log => '🌈' x2
222
222
  emitter.emit('🐶', '🍖'); // log => '🍖'
223
223
  ```
224
224
 
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
+
225
240
  ##### Custom subscribable events
226
241
 
227
242
  Emittery exports some symbols which represent "meta" events that can be passed to `Emitter.on` and similar methods.
@@ -282,11 +297,11 @@ await emitter.emit('🦊', 'c'); // Nothing happens
282
297
 
283
298
  ##### listener(data)
284
299
 
285
- #### once(eventName | eventName[])
300
+ #### once(eventName | eventName[], predicate?)
286
301
 
287
- Subscribe to one or more events only once. It will be unsubscribed after the first event.
302
+ Subscribe to one or more events only once. It will be unsubscribed after the first event that matches the predicate (if provided).
288
303
 
289
- Returns a promise for the event data when `eventName` is emitted. This promise is extended with an `off` method.
304
+ Returns a promise for the event data when `eventName` is emitted and predicate matches (if provided). This promise is extended with an `off` method.
290
305
 
291
306
  ```js
292
307
  import Emittery from 'emittery';
@@ -302,8 +317,16 @@ emitter.once(['🦄', '🐶']).then(data => {
302
317
  console.log(data);
303
318
  });
304
319
 
320
+ // With predicate
321
+ emitter.once('data', data => data.ok === true).then(data => {
322
+ console.log(data);
323
+ //=> {ok: true, value: 42}
324
+ });
325
+
305
326
  emitter.emit('🦄', '🌈'); // Log => '🌈' x2
306
327
  emitter.emit('🐶', '🍖'); // Nothing happens
328
+ emitter.emit('data', {ok: false}); // Nothing happens
329
+ emitter.emit('data', {ok: true, value: 42}); // Log => {ok: true, value: 42}
307
330
  ```
308
331
 
309
332
  #### events(eventName)
@@ -399,11 +422,11 @@ Same as above, but it waits for each listener to resolve before triggering the n
399
422
 
400
423
  If any of the listeners throw/reject, the returned promise will be rejected with the error and the remaining listeners will *not* be called.
401
424
 
402
- #### onAny(listener)
425
+ #### onAny(listener, options?: {signal?: AbortSignal})
403
426
 
404
427
  Subscribe to be notified about any event.
405
428
 
406
- Returns a method to unsubscribe.
429
+ Returns a method to unsubscribe. Abort signal is respected too.
407
430
 
408
431
  ##### listener(eventName, data)
409
432