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