123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- 'use strict';
- const EventEmitter = require('events');
- const once = require('once');
- const ready = require('get-ready');
- const uuid = require('uuid');
- const debug = require('debug')('ready-callback');
- const defaults = {
- timeout: 10000,
- isWeakDep: false,
- };
- /**
- * @class Ready
- */
- class Ready extends EventEmitter {
- /**
- * @constructor
- * @param {Object} opt
- * - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout
- * - {Boolean} [isWeakDep=false] - whether it's a weak dependency
- * - {Boolean} [lazyStart=false] - will not check cache size automatically, if lazyStart is true
- */
- constructor(opt) {
- super();
- ready.mixin(this);
- this.opt = opt || {};
- this.isError = false;
- this.cache = new Map();
- if (!this.opt.lazyStart) {
- this.start();
- }
- }
- start() {
- setImmediate(() => {
- // fire callback directly when no registered ready callback
- if (this.cache.size === 0) {
- debug('Fire callback directly');
- this.ready(true);
- }
- });
- }
- /**
- * Mix `ready` and `readyCallback` to `obj`
- * @method Ready#mixin
- * @param {Object} obj - The mixed object
- * @return {Ready} this
- */
- mixin(obj) {
- // only mixin once
- if (!obj || this.obj) return null;
- // delegate API to object
- obj.ready = this.ready.bind(this);
- obj.readyCallback = this.readyCallback.bind(this);
- // only ready once with error
- this.once('error', err => obj.ready(err));
- // delegate events
- if (obj.emit) {
- this.on('ready_timeout', obj.emit.bind(obj, 'ready_timeout'));
- this.on('ready_stat', obj.emit.bind(obj, 'ready_stat'));
- this.on('error', obj.emit.bind(obj, 'error'));
- }
- this.obj = obj;
- return this;
- }
- /**
- * Create a callback, ready won't be fired until all the callbacks are triggered.
- * @method Ready#readyCallback
- * @param {String} name -
- * @param {Object} opt - the options that will override global
- * @return {Function} - a callback
- */
- readyCallback(name, opt) {
- opt = Object.assign({}, defaults, this.opt, opt);
- const cacheKey = uuid.v1();
- opt.name = name || cacheKey;
- const timer = setTimeout(() => this.emit('ready_timeout', opt.name), opt.timeout);
- const cb = once(err => {
- if (err != null && !(err instanceof Error)) {
- err = new Error(err);
- }
- clearTimeout(timer);
- // won't continue to fire after it's error
- if (this.isError === true) return;
- // fire callback after all register
- setImmediate(() => this.readyDone(cacheKey, opt, err));
- });
- debug('[%s] Register task id `%s` with %j', cacheKey, opt.name, opt);
- cb.id = opt.name;
- this.cache.set(cacheKey, cb);
- return cb;
- }
- /**
- * resolve ths callback when readyCallback be called
- * @method Ready#readyDone
- * @private
- * @param {String} id - unique id generated by readyCallback
- * @param {Object} opt - the options that will override global
- * @param {Error} err - err passed by ready callback
- * @return {Ready} this
- */
- readyDone(id, opt, err) {
- if (err != null && !opt.isWeakDep) {
- this.isError = true;
- debug('[%s] Throw error task id `%s`, error %s', id, opt.name, err);
- return this.emit('error', err);
- }
- debug('[%s] End task id `%s`, error %s', id, opt.name, err);
- this.cache.delete(id);
- this.emit('ready_stat', {
- id: opt.name,
- remain: getRemain(this.cache),
- });
- if (this.cache.size === 0) {
- debug('[%s] Fire callback async', id);
- this.ready(true);
- }
- return this;
- }
- }
- // Use ready-callback with options
- module.exports = opt => new Ready(opt);
- module.exports.Ready = Ready;
- function getRemain(map) {
- const names = [];
- for (const cb of map.values()) {
- names.push(cb.id);
- }
- return names;
- }
|