ready.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. 'use strict';
  2. const EventEmitter = require('events');
  3. const once = require('once');
  4. const ready = require('get-ready');
  5. const uuid = require('uuid');
  6. const debug = require('debug')('ready-callback');
  7. const defaults = {
  8. timeout: 10000,
  9. isWeakDep: false,
  10. };
  11. /**
  12. * @class Ready
  13. */
  14. class Ready extends EventEmitter {
  15. /**
  16. * @constructor
  17. * @param {Object} opt
  18. * - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout
  19. * - {Boolean} [isWeakDep=false] - whether it's a weak dependency
  20. * - {Boolean} [lazyStart=false] - will not check cache size automatically, if lazyStart is true
  21. */
  22. constructor(opt) {
  23. super();
  24. ready.mixin(this);
  25. this.opt = opt || {};
  26. this.isError = false;
  27. this.cache = new Map();
  28. if (!this.opt.lazyStart) {
  29. this.start();
  30. }
  31. }
  32. start() {
  33. setImmediate(() => {
  34. // fire callback directly when no registered ready callback
  35. if (this.cache.size === 0) {
  36. debug('Fire callback directly');
  37. this.ready(true);
  38. }
  39. });
  40. }
  41. /**
  42. * Mix `ready` and `readyCallback` to `obj`
  43. * @method Ready#mixin
  44. * @param {Object} obj - The mixed object
  45. * @return {Ready} this
  46. */
  47. mixin(obj) {
  48. // only mixin once
  49. if (!obj || this.obj) return null;
  50. // delegate API to object
  51. obj.ready = this.ready.bind(this);
  52. obj.readyCallback = this.readyCallback.bind(this);
  53. // only ready once with error
  54. this.once('error', err => obj.ready(err));
  55. // delegate events
  56. if (obj.emit) {
  57. this.on('ready_timeout', obj.emit.bind(obj, 'ready_timeout'));
  58. this.on('ready_stat', obj.emit.bind(obj, 'ready_stat'));
  59. this.on('error', obj.emit.bind(obj, 'error'));
  60. }
  61. this.obj = obj;
  62. return this;
  63. }
  64. /**
  65. * Create a callback, ready won't be fired until all the callbacks are triggered.
  66. * @method Ready#readyCallback
  67. * @param {String} name -
  68. * @param {Object} opt - the options that will override global
  69. * @return {Function} - a callback
  70. */
  71. readyCallback(name, opt) {
  72. opt = Object.assign({}, defaults, this.opt, opt);
  73. const cacheKey = uuid.v1();
  74. opt.name = name || cacheKey;
  75. const timer = setTimeout(() => this.emit('ready_timeout', opt.name), opt.timeout);
  76. const cb = once(err => {
  77. if (err != null && !(err instanceof Error)) {
  78. err = new Error(err);
  79. }
  80. clearTimeout(timer);
  81. // won't continue to fire after it's error
  82. if (this.isError === true) return;
  83. // fire callback after all register
  84. setImmediate(() => this.readyDone(cacheKey, opt, err));
  85. });
  86. debug('[%s] Register task id `%s` with %j', cacheKey, opt.name, opt);
  87. cb.id = opt.name;
  88. this.cache.set(cacheKey, cb);
  89. return cb;
  90. }
  91. /**
  92. * resolve ths callback when readyCallback be called
  93. * @method Ready#readyDone
  94. * @private
  95. * @param {String} id - unique id generated by readyCallback
  96. * @param {Object} opt - the options that will override global
  97. * @param {Error} err - err passed by ready callback
  98. * @return {Ready} this
  99. */
  100. readyDone(id, opt, err) {
  101. if (err != null && !opt.isWeakDep) {
  102. this.isError = true;
  103. debug('[%s] Throw error task id `%s`, error %s', id, opt.name, err);
  104. return this.emit('error', err);
  105. }
  106. debug('[%s] End task id `%s`, error %s', id, opt.name, err);
  107. this.cache.delete(id);
  108. this.emit('ready_stat', {
  109. id: opt.name,
  110. remain: getRemain(this.cache),
  111. });
  112. if (this.cache.size === 0) {
  113. debug('[%s] Fire callback async', id);
  114. this.ready(true);
  115. }
  116. return this;
  117. }
  118. }
  119. // Use ready-callback with options
  120. module.exports = opt => new Ready(opt);
  121. module.exports.Ready = Ready;
  122. function getRemain(map) {
  123. const names = [];
  124. for (const cb of map.values()) {
  125. names.push(cb.id);
  126. }
  127. return names;
  128. }