fcoin/lib/utils/asyncemitter.js
2018-03-29 21:56:47 -07:00

441 lines
8.4 KiB
JavaScript

/*!
* asyncemitter.js - event emitter which resolves promises.
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const assert = require('assert');
/**
* Async Emitter
* @alias module:utils.AsyncEmitter
* @see EventEmitter
*/
class AsyncEmitter {
/**
* Create an async emitter.
* @constructor
*/
constructor() {
this._events = Object.create(null);
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
addListener(type, handler) {
return this._push(type, handler, false);
}
/**
* Add a listener.
* @param {String} type
* @param {Function} handler
*/
on(type, handler) {
return this.addListener(type, handler);
}
/**
* Add a listener to execute once.
* @param {String} type
* @param {Function} handler
*/
once(type, handler) {
return this._push(type, handler, true);
}
/**
* Prepend a listener.
* @param {String} type
* @param {Function} handler
*/
prependListener(type, handler) {
return this._unshift(type, handler, false);
}
/**
* Prepend a listener to execute once.
* @param {String} type
* @param {Function} handler
*/
prependOnceListener(type, handler) {
return this._unshift(type, handler, true);
}
/**
* Push a listener.
* @private
* @param {String} type
* @param {Function} handler
* @param {Boolean} once
*/
_push(type, handler, once) {
assert(typeof type === 'string', '`type` must be a string.');
if (!this._events[type])
this._events[type] = [];
this.emit('newListener', type, handler);
this._events[type].push(new Listener(handler, once));
}
/**
* Unshift a listener.
* @param {String} type
* @param {Function} handler
* @param {Boolean} once
*/
_unshift(type, handler, once) {
assert(typeof type === 'string', '`type` must be a string.');
if (!this._events[type])
this._events[type] = [];
this.emit('newListener', type, handler);
this._events[type].unshift(new Listener(handler, once));
}
/**
* Remove a listener.
* @param {String} type
* @param {Function} handler
*/
removeListener(type, handler) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return;
let index = -1;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
if (listener.handler === handler) {
index = i;
break;
}
}
if (index === -1)
return;
splice(listeners, index);
if (listeners.length === 0)
delete this._events[type];
this.emit('removeListener', type, handler);
}
/**
* Set max listeners.
* @param {Number} max
*/
setMaxListeners(max) {
assert(typeof max === 'number', '`max` must be a number.');
assert(max >= 0, '`max` must be non-negative.');
assert(Number.isSafeInteger(max), '`max` must be an integer.');
}
/**
* Remove all listeners.
* @param {String?} type
*/
removeAllListeners(type) {
if (arguments.length === 0) {
this._events = Object.create(null);
return;
}
assert(typeof type === 'string', '`type` must be a string.');
delete this._events[type];
}
/**
* Get listeners array.
* @param {String} type
* @returns {Function[]}
*/
listeners(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return [];
const result = [];
for (const {handler} of listeners)
result.push(handler);
return result;
}
/**
* Get listener count for an event.
* @param {String} type
*/
listenerCount(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners)
return 0;
return listeners.length;
}
/**
* Get event names.
* @returns {String[]}
*/
eventNames() {
return Object.keys(this._events);
}
/**
* Emit an event synchronously.
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
emit(type) {
try {
this._emit.apply(this, arguments);
} catch (e) {
if (type === 'error')
throw e;
this._emit('error', e);
}
}
/**
* Emit an event synchronously.
* @private
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
_emit(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners) {
if (type === 'error') {
const msg = arguments[1];
if (msg instanceof Error)
throw msg;
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
err.context = msg;
throw err;
}
return;
}
assert(listeners.length > 0);
let args = null;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
const handler = listener.handler;
if (listener.once) {
splice(listeners, i);
if (listeners.length === 0)
delete this._events[type];
i -= 1;
}
switch (arguments.length) {
case 1:
handler();
break;
case 2:
handler(arguments[1]);
break;
case 3:
handler(arguments[1], arguments[2]);
break;
case 4:
handler(arguments[1], arguments[2], arguments[3]);
break;
default:
if (!args) {
args = new Array(arguments.length - 1);
for (let j = 1; j < arguments.length; j++)
args[j - 1] = arguments[j];
}
handler.apply(null, args);
break;
}
}
}
/**
* Emit an event. Wait for promises to resolve.
* @method
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
async emitAsync(type) {
try {
await this._emitAsync.apply(this, arguments);
} catch (e) {
if (type === 'error')
throw e;
await this._emitAsync('error', e);
}
}
/**
* Emit an event. Wait for promises to resolve.
* @private
* @param {String} type
* @param {...Object} args
* @returns {Promise}
*/
async _emitAsync(type) {
assert(typeof type === 'string', '`type` must be a string.');
const listeners = this._events[type];
if (!listeners) {
if (type === 'error') {
const msg = arguments[1];
if (msg instanceof Error)
throw msg;
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
err.context = msg;
throw err;
}
return;
}
assert(listeners.length > 0);
let args = null;
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
const handler = listener.handler;
if (listener.once) {
splice(listeners, i);
if (listeners.length === 0)
delete this._events[type];
i -= 1;
}
switch (arguments.length) {
case 1:
await handler();
break;
case 2:
await handler(arguments[1]);
break;
case 3:
await handler(arguments[1], arguments[2]);
break;
case 4:
await handler(arguments[1], arguments[2], arguments[3]);
break;
default:
if (!args) {
args = new Array(arguments.length - 1);
for (let j = 1; j < arguments.length; j++)
args[j - 1] = arguments[j];
}
await handler.apply(null, args);
break;
}
}
}
}
/**
* Event Listener
* @ignore
* @property {Function} handler
* @property {Boolean} once
*/
class Listener {
/**
* Create an event listener.
* @constructor
* @param {Function} handler
* @param {Boolean} once
*/
constructor(handler, once) {
assert(typeof handler === 'function', '`handler` must be a function.');
assert(typeof once === 'boolean', '`once` must be a function.');
this.handler = handler;
this.once = once;
}
}
/*
* Helpers
*/
function splice(list, i) {
if (i === 0) {
list.shift();
return;
}
let k = i + 1;
while (k < list.length)
list[i++] = list[k++];
list.pop();
}
/*
* Expose
*/
module.exports = AsyncEmitter;