index.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. 'use strict';
  2. const co = require('co');
  3. const util = require('util');
  4. const is = require('is-type-of');
  5. const assert = require('assert');
  6. const awaitEvent = require('await-event');
  7. const awaitFirst = require('await-first');
  8. const EventEmitter = require('events').EventEmitter;
  9. const CLOSE_PROMISE = Symbol('base#closePromise');
  10. class Base extends EventEmitter {
  11. constructor(options) {
  12. super();
  13. if (options && options.initMethod) {
  14. assert(is.function(this[options.initMethod]),
  15. `[sdk-base] this.${options.initMethod} should be a function.`);
  16. process.nextTick(() => {
  17. if (is.generatorFunction(this[options.initMethod])) {
  18. this[options.initMethod] = co.wrap(this[options.initMethod]);
  19. }
  20. const ret = this[options.initMethod]();
  21. assert(is.promise(ret), `[sdk-base] this.${options.initMethod} should return either a promise or a generator`);
  22. ret.then(() => this.ready(true))
  23. .catch(err => this.ready(err));
  24. });
  25. }
  26. this.options = options || {};
  27. this._ready = false;
  28. this._readyError = null;
  29. this._readyCallbacks = [];
  30. this._closed = false;
  31. // support `yield this.await('event')`
  32. this.await = awaitEvent;
  33. this.awaitFirst = awaitFirst;
  34. this.on('error', err => { this._defaultErrorHandler(err); });
  35. }
  36. _wrapListener(eventName, listener) {
  37. if (is.generatorFunction(listener)) {
  38. assert(eventName !== 'error', '[sdk-base] `error` event should not have a generator listener.');
  39. const newListener = (...args) => {
  40. co(function* () {
  41. yield listener(...args);
  42. }).catch(err => {
  43. err.name = 'EventListenerProcessError';
  44. this.emit('error', err);
  45. });
  46. };
  47. newListener.original = listener;
  48. return newListener;
  49. }
  50. return listener;
  51. }
  52. addListener(eventName, listener) {
  53. return super.addListener(eventName, this._wrapListener(eventName, listener));
  54. }
  55. on(eventName, listener) {
  56. return super.on(eventName, this._wrapListener(eventName, listener));
  57. }
  58. once(eventName, listener) {
  59. return super.once(eventName, this._wrapListener(eventName, listener));
  60. }
  61. prependListener(eventName, listener) {
  62. return super.prependListener(eventName, this._wrapListener(eventName, listener));
  63. }
  64. prependOnceListener(eventName, listener) {
  65. return super.prependOnceListener(eventName, this._wrapListener(eventName, listener));
  66. }
  67. removeListener(eventName, listener) {
  68. let target = listener;
  69. if (is.generatorFunction(listener)) {
  70. const listeners = this.listeners(eventName);
  71. for (const fn of listeners) {
  72. if (fn.original === listener) {
  73. target = fn;
  74. break;
  75. }
  76. }
  77. }
  78. return super.removeListener(eventName, target);
  79. }
  80. /**
  81. * detect sdk start ready or not
  82. * @return {Boolean} ready status
  83. */
  84. get isReady() {
  85. return this._ready;
  86. }
  87. /**
  88. * set ready state or onready callback
  89. *
  90. * @param {Boolean|Error|Function} flagOrFunction - ready state or callback function
  91. * @return {void|Promise} ready promise
  92. */
  93. ready(flagOrFunction) {
  94. if (arguments.length === 0) {
  95. // return a promise
  96. // support `this.ready().then(onready);` and `yield this.ready()`;
  97. return new Promise((resolve, reject) => {
  98. if (this._ready) {
  99. return resolve();
  100. } else if (this._readyError) {
  101. return reject(this._readyError);
  102. }
  103. this._readyCallbacks.push(err => {
  104. if (err) {
  105. reject(err);
  106. } else {
  107. resolve();
  108. }
  109. });
  110. });
  111. } else if (is.function(flagOrFunction)) {
  112. this._readyCallbacks.push(flagOrFunction);
  113. } else if (flagOrFunction instanceof Error) {
  114. this._ready = false;
  115. this._readyError = flagOrFunction;
  116. if (!this._readyCallbacks.length) {
  117. this.emit('error', flagOrFunction);
  118. }
  119. } else {
  120. this._ready = flagOrFunction;
  121. }
  122. if (this._ready || this._readyError) {
  123. this._readyCallbacks.splice(0, Infinity).forEach(callback => {
  124. process.nextTick(() => {
  125. callback(this._readyError);
  126. });
  127. });
  128. }
  129. }
  130. _defaultErrorHandler(err) {
  131. if (this.listeners('error').length > 1) {
  132. // ignore defaultErrorHandler
  133. return;
  134. }
  135. console.error('\n[%s][pid: %s][%s] %s: %s \nError Stack:\n %s',
  136. Date(), process.pid, this.constructor.name, err.name,
  137. err.message, err.stack);
  138. // try to show addition property on the error object
  139. // e.g.: `err.data = {url: '/foo'};`
  140. const additions = [];
  141. for (const key in err) {
  142. if (key === 'name' || key === 'message') {
  143. continue;
  144. }
  145. additions.push(util.format(' %s: %j', key, err[key]));
  146. }
  147. if (additions.length) {
  148. console.error('Error Additions:\n%s', additions.join('\n'));
  149. }
  150. console.error();
  151. }
  152. close() {
  153. if (this._closed) {
  154. return Promise.resolve();
  155. }
  156. if (this[CLOSE_PROMISE]) {
  157. return this[CLOSE_PROMISE];
  158. }
  159. if (!this._close) {
  160. this._closed = true;
  161. return Promise.resolve();
  162. }
  163. let closeFunc = this._close;
  164. if (is.generatorFunction(closeFunc)) {
  165. closeFunc = co.wrap(closeFunc);
  166. }
  167. this[CLOSE_PROMISE] = closeFunc.apply(this);
  168. assert(is.promise(this[CLOSE_PROMISE]), '[sdk-base] this._close should return either a promise or a generator');
  169. return this[CLOSE_PROMISE]
  170. .then(() => {
  171. this._closed = true;
  172. })
  173. .catch(err => {
  174. this._closed = true;
  175. this.emit('error', err);
  176. });
  177. }
  178. }
  179. module.exports = Base;
  180. // support es module
  181. module.exports.default = Base;