EventEmitter.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @noflow
  9. * @typecheck
  10. */
  11. 'use strict';
  12. const EmitterSubscription = require('./EmitterSubscription');
  13. const EventSubscriptionVendor = require('./EventSubscriptionVendor');
  14. const invariant = require('invariant');
  15. const sparseFilterPredicate = () => true;
  16. /**
  17. * @class EventEmitter
  18. * @description
  19. * An EventEmitter is responsible for managing a set of listeners and publishing
  20. * events to them when it is told that such events happened. In addition to the
  21. * data for the given event it also sends a event control object which allows
  22. * the listeners/handlers to prevent the default behavior of the given event.
  23. *
  24. * The emitter is designed to be generic enough to support all the different
  25. * contexts in which one might want to emit events. It is a simple multicast
  26. * mechanism on top of which extra functionality can be composed. For example, a
  27. * more advanced emitter may use an EventHolder and EventFactory.
  28. */
  29. class EventEmitter {
  30. _subscriber: EventSubscriptionVendor;
  31. _currentSubscription: ?EmitterSubscription;
  32. /**
  33. * @constructor
  34. *
  35. * @param {EventSubscriptionVendor} subscriber - Optional subscriber instance
  36. * to use. If omitted, a new subscriber will be created for the emitter.
  37. */
  38. constructor(subscriber: ?EventSubscriptionVendor) {
  39. this._subscriber = subscriber || new EventSubscriptionVendor();
  40. }
  41. /**
  42. * Adds a listener to be invoked when events of the specified type are
  43. * emitted. An optional calling context may be provided. The data arguments
  44. * emitted will be passed to the listener function.
  45. *
  46. * TODO: Annotate the listener arg's type. This is tricky because listeners
  47. * can be invoked with varargs.
  48. *
  49. * @param {string} eventType - Name of the event to listen to
  50. * @param {function} listener - Function to invoke when the specified event is
  51. * emitted
  52. * @param {*} context - Optional context object to use when invoking the
  53. * listener
  54. */
  55. addListener(
  56. eventType: string,
  57. listener: Function,
  58. context: ?Object,
  59. ): EmitterSubscription {
  60. return (this._subscriber.addSubscription(
  61. eventType,
  62. new EmitterSubscription(this, this._subscriber, listener, context),
  63. ): any);
  64. }
  65. /**
  66. * Similar to addListener, except that the listener is removed after it is
  67. * invoked once.
  68. *
  69. * @param {string} eventType - Name of the event to listen to
  70. * @param {function} listener - Function to invoke only once when the
  71. * specified event is emitted
  72. * @param {*} context - Optional context object to use when invoking the
  73. * listener
  74. */
  75. once(
  76. eventType: string,
  77. listener: Function,
  78. context: ?Object,
  79. ): EmitterSubscription {
  80. return this.addListener(eventType, (...args) => {
  81. this.removeCurrentListener();
  82. listener.apply(context, args);
  83. });
  84. }
  85. /**
  86. * Removes all of the registered listeners, including those registered as
  87. * listener maps.
  88. *
  89. * @param {?string} eventType - Optional name of the event whose registered
  90. * listeners to remove
  91. */
  92. removeAllListeners(eventType: ?string) {
  93. this._subscriber.removeAllSubscriptions(eventType);
  94. }
  95. /**
  96. * Provides an API that can be called during an eventing cycle to remove the
  97. * last listener that was invoked. This allows a developer to provide an event
  98. * object that can remove the listener (or listener map) during the
  99. * invocation.
  100. *
  101. * If it is called when not inside of an emitting cycle it will throw.
  102. *
  103. * @throws {Error} When called not during an eventing cycle
  104. *
  105. * @example
  106. * var subscription = emitter.addListenerMap({
  107. * someEvent: function(data, event) {
  108. * console.log(data);
  109. * emitter.removeCurrentListener();
  110. * }
  111. * });
  112. *
  113. * emitter.emit('someEvent', 'abc'); // logs 'abc'
  114. * emitter.emit('someEvent', 'def'); // does not log anything
  115. */
  116. removeCurrentListener() {
  117. invariant(
  118. !!this._currentSubscription,
  119. 'Not in an emitting cycle; there is no current subscription',
  120. );
  121. this.removeSubscription(this._currentSubscription);
  122. }
  123. /**
  124. * Removes a specific subscription. Called by the `remove()` method of the
  125. * subscription itself to ensure any necessary cleanup is performed.
  126. */
  127. removeSubscription(subscription: EmitterSubscription) {
  128. invariant(
  129. subscription.emitter === this,
  130. 'Subscription does not belong to this emitter.',
  131. );
  132. this._subscriber.removeSubscription(subscription);
  133. }
  134. /**
  135. * Returns an array of listeners that are currently registered for the given
  136. * event.
  137. *
  138. * @param {string} eventType - Name of the event to query
  139. * @returns {array}
  140. */
  141. listeners(eventType: string): [EmitterSubscription] {
  142. const subscriptions = this._subscriber.getSubscriptionsForType(eventType);
  143. return subscriptions
  144. ? subscriptions
  145. // We filter out missing entries because the array is sparse.
  146. // "callbackfn is called only for elements of the array which actually
  147. // exist; it is not called for missing elements of the array."
  148. // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-array.prototype.filter
  149. .filter(sparseFilterPredicate)
  150. .map(subscription => subscription.listener)
  151. : [];
  152. }
  153. /**
  154. * Emits an event of the given type with the given data. All handlers of that
  155. * particular type will be notified.
  156. *
  157. * @param {string} eventType - Name of the event to emit
  158. * @param {...*} Arbitrary arguments to be passed to each registered listener
  159. *
  160. * @example
  161. * emitter.addListener('someEvent', function(message) {
  162. * console.log(message);
  163. * });
  164. *
  165. * emitter.emit('someEvent', 'abc'); // logs 'abc'
  166. */
  167. emit(eventType: string) {
  168. const subscriptions = this._subscriber.getSubscriptionsForType(eventType);
  169. if (subscriptions) {
  170. for (let i = 0, l = subscriptions.length; i < l; i++) {
  171. const subscription = subscriptions[i];
  172. // The subscription may have been removed during this event loop.
  173. if (subscription && subscription.listener) {
  174. this._currentSubscription = subscription;
  175. subscription.listener.apply(
  176. subscription.context,
  177. Array.prototype.slice.call(arguments, 1),
  178. );
  179. }
  180. }
  181. this._currentSubscription = null;
  182. }
  183. }
  184. /**
  185. * Removes the given listener for event of specific type.
  186. *
  187. * @param {string} eventType - Name of the event to emit
  188. * @param {function} listener - Function to invoke when the specified event is
  189. * emitted
  190. *
  191. * @example
  192. * emitter.removeListener('someEvent', function(message) {
  193. * console.log(message);
  194. * }); // removes the listener if already registered
  195. *
  196. */
  197. removeListener(eventType: String, listener) {
  198. const subscriptions = this._subscriber.getSubscriptionsForType(eventType);
  199. if (subscriptions) {
  200. for (let i = 0, l = subscriptions.length; i < l; i++) {
  201. const subscription = subscriptions[i];
  202. // The subscription may have been removed during this event loop.
  203. // its listener matches the listener in method parameters
  204. if (subscription && subscription.listener === listener) {
  205. subscription.remove();
  206. }
  207. }
  208. }
  209. }
  210. }
  211. module.exports = EventEmitter;