watchdog
Version:
An Timer used to Detect and Recover from Malfunctions
218 lines (213 loc) • 8.12 kB
JavaScript
/* watchdog version 0.8.17 */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('events'), require('brolog')) :
typeof define === 'function' && define.amd ? define(['exports', 'events', 'brolog'], factory) :
(factory((global.window = {}),global.events,global.brolog));
}(this, (function (exports,events,brolog) { 'use strict';
// https://github.com/andrew-filonenko/ya-watchdog
const log = new brolog.Brolog();
const VERSION = require('../package.json').version;
class Watchdog extends events.EventEmitter {
/**
* A Timer used to detect and recover from malfunctions
*
* @class Watchdog
* @param {number} [defaultTimeout=60 * 1000]
* @param {string} [name='Bark']
* @example
* const TIMEOUT = 1 * 1000 // 1 second
* const dog = new watchdog(TIMEOUT)
*
* const food = { data: 'delicious' }
*
* dog.on('reset', () => console.log('reset-ed'))
* dog.on('feed', () => console.log('feed-ed'))
*
* dog.feed(food)
* // Output: feed-ed
*
* setTimeout(function() {
* dog.sleep()
* console.log('dog sleep-ed. Demo over.')
* }, TIMEOUT + 1)
* // Output: reset-ed.
* // Output: dog sleep-ed. Demo over.
*
*/
constructor(defaultTimeout = 60 * 1000, name = 'Bark') {
super();
this.defaultTimeout = defaultTimeout;
this.name = name;
log.verbose('Watchdog', '<%s>: constructor(name=%s, defaultTimeout=%d)', name, name, defaultTimeout);
}
version() {
return VERSION;
}
/**
* @desc Watchdog Class Event Type
* @typedef WatchdogEvent
* @property { string } feed - Emit when feed the dog.
* @property { string } reset - Emit when timeout and reset.
* @property { string } sleep - Emit when timer is cleared out.
*/
/**
* @desc Watchdog Class Event Function
* @typedef WatchdogListener
* @property { Function } - (food: WatchdogFood<T, D>, left: number) => void
*/
/**
* @listens Watchdog
* @param { WatchdogEvent } event
* @param { WatchdogListener<T, D> } listener
* @returns { this }
*
* @example <caption>Event:reset </caption>
* dog.on('reset', () => console.log('reset-ed'))
* @example <caption>Event:feed </caption>
* dog.on('feed', () => console.log('feed-ed'))
* @example <caption>Event:sleep </caption>
* dog.on('sleep', () => console.log('sleep-ed'))
*
*/
on(event, listener) {
log.verbose('Watchdog', '<%s> on(%s, listener) registered.', this.name, event);
super.on(event, listener);
return this;
}
startTimer(timeout) {
log.verbose('Watchdog', '<%s> startTimer()', this.name);
if (this.timer) {
throw new Error('timer already exist!');
}
this.timer = setTimeout(() => {
log.verbose('Watchdog', '<%s> startTimer() setTimeout() after %d', this.name, timeout);
this.timer = undefined; // sleep after reset
this.emit('reset', this.lastFood, this.lastFood && this.lastFood.timeout || this.defaultTimeout);
}, timeout);
this.timer.unref(); // should not block node quit
return;
}
stopTimer(sleep = false) {
log.verbose('Watchdog', '<%s> stopTimer()', this.name);
if (typeof this.timer === 'undefined') { // first time
log.verbose('Watchdog', '<%s> stopTimer() first run(or after sleep)', this.name);
return;
}
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
else if (!sleep) {
throw new Error('timer is already stoped!');
}
}
/**
* Get the left time
* @returns {number}
*/
left() {
let left;
if (typeof this.lastFeed !== 'undefined'
&& Number.isInteger(this.lastFeed)) {
// console.log('lastFeed=', this.lastFeed)
// console.log('timeout=', this.lastFood.timeout)
// console.log('Date.now()=', Date.now())
left = this.lastFeed + this.defaultTimeout - Date.now();
log.verbose('Watchdog', '<%s> timerLeft() = %d', this.name, left);
}
else {
left = 0;
log.verbose('Watchdog', '<%s> timerLeft() first feed, left=%s', this.name, left);
}
return left;
}
/**
* Dog Feed content
*
* @typedef WatchdogFood
* @property {D} data - feed content.
* @property {number} timeout - option, set timeout.
* @property {T} type - option.
*/
/**
* feed the dog
* @param {WatchdogFood} food
* @returns {number}
* @example
* const food = {
* data: 'delicious',
* timeout: 1 * 1000,
* }
* const dog = new Watchdog()
* dog.feed(food)
*/
feed(food) {
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// https://stackoverflow.com/a/11616993/1123955
function replacerFactory() {
// Note: cache should not be re-used by repeated calls to JSON.stringify.
const cache = [];
return function (_, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
return;
}
// Store value in our collection
cache.push(value);
}
return value;
};
}
log.verbose('Watchdog', '<%s> feed(%s)', this.name, JSON.stringify(food, replacerFactory()));
if (typeof food !== 'object') {
/**
* weak typing compitable:
* if user call watchdog.feed('string'), we need to pre-processs the food to a object.
* or we will meet a exception: we can not set property on string type.
*/
food = {
data: food,
};
}
if (!food.timeout) {
food.timeout = this.defaultTimeout;
}
const left = this.left();
this.stopTimer();
this.startTimer(food.timeout);
this.lastFeed = Date.now();
this.lastFood = food;
this.emit('feed', food, left);
return left;
}
/**
* Clear timer.
* @example
* const dog = new Watchdog()
* dog.sleep()
*/
sleep() {
log.verbose('Watchdog', '<%s> sleep()', this.name);
this.stopTimer(true);
this.timer = undefined;
this.emit('sleep', this.lastFood, this.left());
}
/**
*
*/
unref() {
log.verbose('Watchdog', '<%s> unref()', this.name);
if (this.timer) {
this.timer.unref();
}
}
}
exports.log = log;
exports.VERSION = VERSION;
exports.Watchdog = Watchdog;
exports.default = Watchdog;
Object.defineProperty(exports, '__esModule', { value: true });
})));
/* https://github.com/huan */
//# sourceMappingURL=watchdog.es6.umd.js.map