index.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. 'use strict';
  2. const anyMap = new WeakMap();
  3. const eventsMap = new WeakMap();
  4. const producersMap = new WeakMap();
  5. const anyProducer = Symbol('anyProducer');
  6. const resolvedPromise = Promise.resolve();
  7. const listenerAdded = Symbol('listenerAdded');
  8. const listenerRemoved = Symbol('listenerRemoved');
  9. function assertEventName(eventName) {
  10. if (typeof eventName !== 'string' && typeof eventName !== 'symbol') {
  11. throw new TypeError('eventName must be a string or a symbol');
  12. }
  13. }
  14. function assertListener(listener) {
  15. if (typeof listener !== 'function') {
  16. throw new TypeError('listener must be a function');
  17. }
  18. }
  19. function getListeners(instance, eventName) {
  20. const events = eventsMap.get(instance);
  21. if (!events.has(eventName)) {
  22. events.set(eventName, new Set());
  23. }
  24. return events.get(eventName);
  25. }
  26. function getEventProducers(instance, eventName) {
  27. const key = typeof eventName === 'string' || typeof eventName === 'symbol' ? eventName : anyProducer;
  28. const producers = producersMap.get(instance);
  29. if (!producers.has(key)) {
  30. producers.set(key, new Set());
  31. }
  32. return producers.get(key);
  33. }
  34. function enqueueProducers(instance, eventName, eventData) {
  35. const producers = producersMap.get(instance);
  36. if (producers.has(eventName)) {
  37. for (const producer of producers.get(eventName)) {
  38. producer.enqueue(eventData);
  39. }
  40. }
  41. if (producers.has(anyProducer)) {
  42. const item = Promise.all([eventName, eventData]);
  43. for (const producer of producers.get(anyProducer)) {
  44. producer.enqueue(item);
  45. }
  46. }
  47. }
  48. function iterator(instance, eventNames) {
  49. eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
  50. let isFinished = false;
  51. let flush = () => {};
  52. let queue = [];
  53. const producer = {
  54. enqueue(item) {
  55. queue.push(item);
  56. flush();
  57. },
  58. finish() {
  59. isFinished = true;
  60. flush();
  61. }
  62. };
  63. for (const eventName of eventNames) {
  64. getEventProducers(instance, eventName).add(producer);
  65. }
  66. return {
  67. async next() {
  68. if (!queue) {
  69. return {done: true};
  70. }
  71. if (queue.length === 0) {
  72. if (isFinished) {
  73. queue = undefined;
  74. return this.next();
  75. }
  76. await new Promise(resolve => {
  77. flush = resolve;
  78. });
  79. return this.next();
  80. }
  81. return {
  82. done: false,
  83. value: await queue.shift()
  84. };
  85. },
  86. async return(value) {
  87. queue = undefined;
  88. for (const eventName of eventNames) {
  89. getEventProducers(instance, eventName).delete(producer);
  90. }
  91. flush();
  92. return arguments.length > 0 ?
  93. {done: true, value: await value} :
  94. {done: true};
  95. },
  96. [Symbol.asyncIterator]() {
  97. return this;
  98. }
  99. };
  100. }
  101. function defaultMethodNamesOrAssert(methodNames) {
  102. if (methodNames === undefined) {
  103. return allEmitteryMethods;
  104. }
  105. if (!Array.isArray(methodNames)) {
  106. throw new TypeError('`methodNames` must be an array of strings');
  107. }
  108. for (const methodName of methodNames) {
  109. if (!allEmitteryMethods.includes(methodName)) {
  110. if (typeof methodName !== 'string') {
  111. throw new TypeError('`methodNames` element must be a string');
  112. }
  113. throw new Error(`${methodName} is not Emittery method`);
  114. }
  115. }
  116. return methodNames;
  117. }
  118. const isListenerSymbol = symbol => symbol === listenerAdded || symbol === listenerRemoved;
  119. class Emittery {
  120. static mixin(emitteryPropertyName, methodNames) {
  121. methodNames = defaultMethodNamesOrAssert(methodNames);
  122. return target => {
  123. if (typeof target !== 'function') {
  124. throw new TypeError('`target` must be function');
  125. }
  126. for (const methodName of methodNames) {
  127. if (target.prototype[methodName] !== undefined) {
  128. throw new Error(`The property \`${methodName}\` already exists on \`target\``);
  129. }
  130. }
  131. function getEmitteryProperty() {
  132. Object.defineProperty(this, emitteryPropertyName, {
  133. enumerable: false,
  134. value: new Emittery()
  135. });
  136. return this[emitteryPropertyName];
  137. }
  138. Object.defineProperty(target.prototype, emitteryPropertyName, {
  139. enumerable: false,
  140. get: getEmitteryProperty
  141. });
  142. const emitteryMethodCaller = methodName => function (...args) {
  143. return this[emitteryPropertyName][methodName](...args);
  144. };
  145. for (const methodName of methodNames) {
  146. Object.defineProperty(target.prototype, methodName, {
  147. enumerable: false,
  148. value: emitteryMethodCaller(methodName)
  149. });
  150. }
  151. return target;
  152. };
  153. }
  154. constructor() {
  155. anyMap.set(this, new Set());
  156. eventsMap.set(this, new Map());
  157. producersMap.set(this, new Map());
  158. }
  159. on(eventNames, listener) {
  160. assertListener(listener);
  161. eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
  162. for (const eventName of eventNames) {
  163. assertEventName(eventName);
  164. getListeners(this, eventName).add(listener);
  165. if (!isListenerSymbol(eventName)) {
  166. this.emit(listenerAdded, {eventName, listener});
  167. }
  168. }
  169. return this.off.bind(this, eventNames, listener);
  170. }
  171. off(eventNames, listener) {
  172. assertListener(listener);
  173. eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
  174. for (const eventName of eventNames) {
  175. assertEventName(eventName);
  176. getListeners(this, eventName).delete(listener);
  177. if (!isListenerSymbol(eventName)) {
  178. this.emit(listenerRemoved, {eventName, listener});
  179. }
  180. }
  181. }
  182. once(eventNames) {
  183. return new Promise(resolve => {
  184. const off = this.on(eventNames, data => {
  185. off();
  186. resolve(data);
  187. });
  188. });
  189. }
  190. events(eventNames) {
  191. eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
  192. for (const eventName of eventNames) {
  193. assertEventName(eventName);
  194. }
  195. return iterator(this, eventNames);
  196. }
  197. async emit(eventName, eventData) {
  198. assertEventName(eventName);
  199. enqueueProducers(this, eventName, eventData);
  200. const listeners = getListeners(this, eventName);
  201. const anyListeners = anyMap.get(this);
  202. const staticListeners = [...listeners];
  203. const staticAnyListeners = isListenerSymbol(eventName) ? [] : [...anyListeners];
  204. await resolvedPromise;
  205. await Promise.all([
  206. ...staticListeners.map(async listener => {
  207. if (listeners.has(listener)) {
  208. return listener(eventData);
  209. }
  210. }),
  211. ...staticAnyListeners.map(async listener => {
  212. if (anyListeners.has(listener)) {
  213. return listener(eventName, eventData);
  214. }
  215. })
  216. ]);
  217. }
  218. async emitSerial(eventName, eventData) {
  219. assertEventName(eventName);
  220. const listeners = getListeners(this, eventName);
  221. const anyListeners = anyMap.get(this);
  222. const staticListeners = [...listeners];
  223. const staticAnyListeners = [...anyListeners];
  224. await resolvedPromise;
  225. /* eslint-disable no-await-in-loop */
  226. for (const listener of staticListeners) {
  227. if (listeners.has(listener)) {
  228. await listener(eventData);
  229. }
  230. }
  231. for (const listener of staticAnyListeners) {
  232. if (anyListeners.has(listener)) {
  233. await listener(eventName, eventData);
  234. }
  235. }
  236. /* eslint-enable no-await-in-loop */
  237. }
  238. onAny(listener) {
  239. assertListener(listener);
  240. anyMap.get(this).add(listener);
  241. this.emit(listenerAdded, {listener});
  242. return this.offAny.bind(this, listener);
  243. }
  244. anyEvent() {
  245. return iterator(this);
  246. }
  247. offAny(listener) {
  248. assertListener(listener);
  249. this.emit(listenerRemoved, {listener});
  250. anyMap.get(this).delete(listener);
  251. }
  252. clearListeners(eventNames) {
  253. eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
  254. for (const eventName of eventNames) {
  255. if (typeof eventName === 'string' || typeof eventName === 'symbol') {
  256. getListeners(this, eventName).clear();
  257. const producers = getEventProducers(this, eventName);
  258. for (const producer of producers) {
  259. producer.finish();
  260. }
  261. producers.clear();
  262. } else {
  263. anyMap.get(this).clear();
  264. for (const listeners of eventsMap.get(this).values()) {
  265. listeners.clear();
  266. }
  267. for (const producers of producersMap.get(this).values()) {
  268. for (const producer of producers) {
  269. producer.finish();
  270. }
  271. producers.clear();
  272. }
  273. }
  274. }
  275. }
  276. listenerCount(eventNames) {
  277. eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
  278. let count = 0;
  279. for (const eventName of eventNames) {
  280. if (typeof eventName === 'string') {
  281. count += anyMap.get(this).size + getListeners(this, eventName).size +
  282. getEventProducers(this, eventName).size + getEventProducers(this).size;
  283. continue;
  284. }
  285. if (typeof eventName !== 'undefined') {
  286. assertEventName(eventName);
  287. }
  288. count += anyMap.get(this).size;
  289. for (const value of eventsMap.get(this).values()) {
  290. count += value.size;
  291. }
  292. for (const value of producersMap.get(this).values()) {
  293. count += value.size;
  294. }
  295. }
  296. return count;
  297. }
  298. bindMethods(target, methodNames) {
  299. if (typeof target !== 'object' || target === null) {
  300. throw new TypeError('`target` must be an object');
  301. }
  302. methodNames = defaultMethodNamesOrAssert(methodNames);
  303. for (const methodName of methodNames) {
  304. if (target[methodName] !== undefined) {
  305. throw new Error(`The property \`${methodName}\` already exists on \`target\``);
  306. }
  307. Object.defineProperty(target, methodName, {
  308. enumerable: false,
  309. value: this[methodName].bind(this)
  310. });
  311. }
  312. }
  313. }
  314. const allEmitteryMethods = Object.getOwnPropertyNames(Emittery.prototype).filter(v => v !== 'constructor');
  315. // Subclass used to encourage TS users to type their events.
  316. Emittery.Typed = class extends Emittery {};
  317. Object.defineProperty(Emittery.Typed, 'Typed', {
  318. enumerable: false,
  319. value: undefined
  320. });
  321. Object.defineProperty(Emittery, 'listenerAdded', {
  322. value: listenerAdded,
  323. writable: false,
  324. enumerable: true,
  325. configurable: false
  326. });
  327. Object.defineProperty(Emittery, 'listenerRemoved', {
  328. value: listenerRemoved,
  329. writable: false,
  330. enumerable: true,
  331. configurable: false
  332. });
  333. module.exports = Emittery;