'use strict';
|
|
const co = require('co');
|
const util = require('util');
|
const is = require('is-type-of');
|
const assert = require('assert');
|
const awaitEvent = require('await-event');
|
const awaitFirst = require('await-first');
|
const EventEmitter = require('events').EventEmitter;
|
const CLOSE_PROMISE = Symbol('base#closePromise');
|
|
class Base extends EventEmitter {
|
constructor(options) {
|
super();
|
|
if (options && options.initMethod) {
|
assert(is.function(this[options.initMethod]),
|
`[sdk-base] this.${options.initMethod} should be a function.`);
|
|
process.nextTick(() => {
|
if (is.generatorFunction(this[options.initMethod])) {
|
this[options.initMethod] = co.wrap(this[options.initMethod]);
|
}
|
const ret = this[options.initMethod]();
|
assert(is.promise(ret), `[sdk-base] this.${options.initMethod} should return either a promise or a generator`);
|
ret.then(() => this.ready(true))
|
.catch(err => this.ready(err));
|
});
|
}
|
this.options = options || {};
|
this._ready = false;
|
this._readyError = null;
|
this._readyCallbacks = [];
|
this._closed = false;
|
|
// support `yield this.await('event')`
|
this.await = awaitEvent;
|
this.awaitFirst = awaitFirst;
|
|
this.on('error', err => { this._defaultErrorHandler(err); });
|
}
|
|
_wrapListener(eventName, listener) {
|
if (is.generatorFunction(listener)) {
|
assert(eventName !== 'error', '[sdk-base] `error` event should not have a generator listener.');
|
|
const newListener = (...args) => {
|
co(function* () {
|
yield listener(...args);
|
}).catch(err => {
|
err.name = 'EventListenerProcessError';
|
this.emit('error', err);
|
});
|
};
|
newListener.original = listener;
|
return newListener;
|
}
|
return listener;
|
}
|
|
addListener(eventName, listener) {
|
return super.addListener(eventName, this._wrapListener(eventName, listener));
|
}
|
|
on(eventName, listener) {
|
return super.on(eventName, this._wrapListener(eventName, listener));
|
}
|
|
once(eventName, listener) {
|
return super.once(eventName, this._wrapListener(eventName, listener));
|
}
|
|
prependListener(eventName, listener) {
|
return super.prependListener(eventName, this._wrapListener(eventName, listener));
|
}
|
|
prependOnceListener(eventName, listener) {
|
return super.prependOnceListener(eventName, this._wrapListener(eventName, listener));
|
}
|
|
removeListener(eventName, listener) {
|
let target = listener;
|
if (is.generatorFunction(listener)) {
|
const listeners = this.listeners(eventName);
|
for (const fn of listeners) {
|
if (fn.original === listener) {
|
target = fn;
|
break;
|
}
|
}
|
}
|
return super.removeListener(eventName, target);
|
}
|
|
/**
|
* detect sdk start ready or not
|
* @return {Boolean} ready status
|
*/
|
get isReady() {
|
return this._ready;
|
}
|
|
/**
|
* set ready state or onready callback
|
*
|
* @param {Boolean|Error|Function} flagOrFunction - ready state or callback function
|
* @return {void|Promise} ready promise
|
*/
|
ready(flagOrFunction) {
|
if (arguments.length === 0) {
|
// return a promise
|
// support `this.ready().then(onready);` and `yield this.ready()`;
|
return new Promise((resolve, reject) => {
|
if (this._ready) {
|
return resolve();
|
} else if (this._readyError) {
|
return reject(this._readyError);
|
}
|
this._readyCallbacks.push(err => {
|
if (err) {
|
reject(err);
|
} else {
|
resolve();
|
}
|
});
|
});
|
} else if (is.function(flagOrFunction)) {
|
this._readyCallbacks.push(flagOrFunction);
|
} else if (flagOrFunction instanceof Error) {
|
this._ready = false;
|
this._readyError = flagOrFunction;
|
if (!this._readyCallbacks.length) {
|
this.emit('error', flagOrFunction);
|
}
|
} else {
|
this._ready = flagOrFunction;
|
}
|
|
if (this._ready || this._readyError) {
|
this._readyCallbacks.splice(0, Infinity).forEach(callback => {
|
process.nextTick(() => {
|
callback(this._readyError);
|
});
|
});
|
}
|
}
|
|
_defaultErrorHandler(err) {
|
if (this.listeners('error').length > 1) {
|
// ignore defaultErrorHandler
|
return;
|
}
|
console.error('\n[%s][pid: %s][%s] %s: %s \nError Stack:\n %s',
|
Date(), process.pid, this.constructor.name, err.name,
|
err.message, err.stack);
|
|
// try to show addition property on the error object
|
// e.g.: `err.data = {url: '/foo'};`
|
const additions = [];
|
for (const key in err) {
|
if (key === 'name' || key === 'message') {
|
continue;
|
}
|
|
additions.push(util.format(' %s: %j', key, err[key]));
|
}
|
if (additions.length) {
|
console.error('Error Additions:\n%s', additions.join('\n'));
|
}
|
console.error();
|
}
|
|
close() {
|
if (this._closed) {
|
return Promise.resolve();
|
}
|
if (this[CLOSE_PROMISE]) {
|
return this[CLOSE_PROMISE];
|
}
|
if (!this._close) {
|
this._closed = true;
|
return Promise.resolve();
|
}
|
let closeFunc = this._close;
|
if (is.generatorFunction(closeFunc)) {
|
closeFunc = co.wrap(closeFunc);
|
}
|
this[CLOSE_PROMISE] = closeFunc.apply(this);
|
assert(is.promise(this[CLOSE_PROMISE]), '[sdk-base] this._close should return either a promise or a generator');
|
return this[CLOSE_PROMISE]
|
.then(() => {
|
this._closed = true;
|
})
|
.catch(err => {
|
this._closed = true;
|
this.emit('error', err);
|
});
|
}
|
}
|
|
module.exports = Base;
|
// support es module
|
module.exports.default = Base;
|