123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- '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;
|