UNPKG

tick-tock

Version:

Timer management, never forget to clear timers again

276 lines (234 loc) 5.85 kB
'use strict'; var has = Object.prototype.hasOwnProperty , ms = require('millisecond'); /** * Timer instance. * * @constructor * @param {Object} timer New timer instance. * @param {Function} clear Clears the timer instance. * @param {Function} duration Duration of the timer. * @param {Function} fn The functions that need to be executed. * @api private */ function Timer(timer, clear, duration, fn) { this.start = +(new Date()); this.duration = duration; this.clear = clear; this.timer = timer; this.fns = [fn]; } /** * Calculate the time left for a given timer. * * @returns {Number} Time in milliseconds. * @api public */ Timer.prototype.remaining = function remaining() { return this.duration - this.taken(); }; /** * Calculate the amount of time it has taken since we've set the timer. * * @returns {Number} * @api public */ Timer.prototype.taken = function taken() { return +(new Date()) - this.start; }; /** * Custom wrappers for the various of clear{whatever} functions. We cannot * invoke them directly as this will cause thrown errors in Google Chrome with * an Illegal Invocation Error * * @see #2 * @type {Function} * @api private */ function unsetTimeout(id) { clearTimeout(id); } function unsetInterval(id) { clearInterval(id); } function unsetImmediate(id) { clearImmediate(id); } /** * Simple timer management. * * @constructor * @param {Mixed} context Context of the callbacks that we execute. * @api public */ function Tick(context) { if (!(this instanceof Tick)) return new Tick(context); this.timers = {}; this.context = context || this; } /** * Return a function which will just iterate over all assigned callbacks and * optionally clear the timers from memory if needed. * * @param {String} name Name of the timer we need to execute. * @param {Boolean} clear Also clear from memory. * @returns {Function} * @api private */ Tick.prototype.tock = function ticktock(name, clear) { var tock = this; return function tickedtock() { if (!(name in tock.timers)) return; var timer = tock.timers[name] , fns = timer.fns.slice() , l = fns.length , i = 0; if (clear) tock.clear(name); else tock.start = +new Date(); for (; i < l; i++) { fns[i].call(tock.context); } }; }; /** * Add a new timeout. * * @param {String} name Name of the timer. * @param {Function} fn Completion callback. * @param {Mixed} time Duration of the timer. * @returns {Tick} * @api public */ Tick.prototype.setTimeout = function timeout(name, fn, time) { var tick = this , tock; if (tick.timers[name]) { tick.timers[name].fns.push(fn); return tick; } tock = ms(time); tick.timers[name] = new Timer( setTimeout(tick.tock(name, true), ms(time)), unsetTimeout, tock, fn ); return tick; }; /** * Add a new interval. * * @param {String} name Name of the timer. * @param {Function} fn Completion callback. * @param {Mixed} time Interval of the timer. * @returns {Tick} * @api public */ Tick.prototype.setInterval = function interval(name, fn, time) { var tick = this , tock; if (tick.timers[name]) { tick.timers[name].fns.push(fn); return tick; } tock = ms(time); tick.timers[name] = new Timer( setInterval(tick.tock(name), ms(time)), unsetInterval, tock, fn ); return tick; }; /** * Add a new setImmediate. * * @param {String} name Name of the timer. * @param {Function} fn Completion callback. * @returns {Tick} * @api public */ Tick.prototype.setImmediate = function immediate(name, fn) { var tick = this; if ('function' !== typeof setImmediate) return tick.setTimeout(name, fn, 0); if (tick.timers[name]) { tick.timers[name].fns.push(fn); return tick; } tick.timers[name] = new Timer( setImmediate(tick.tock(name, true)), unsetImmediate, 0, fn ); return tick; }; /** * Check if we have a timer set. * * @param {String} name * @returns {Boolean} * @api public */ Tick.prototype.active = function active(name) { return name in this.timers; }; /** * Properly clean up all timeout references. If no arguments are supplied we * will attempt to clear every single timer that is present. * * @param {Arguments} ..args.. The names of the timeouts we need to clear * @returns {Tick} * @api public */ Tick.prototype.clear = function clear() { var args = arguments.length ? arguments : [] , tick = this , timer, i, l; if (args.length === 1 && 'string' === typeof args[0]) { args = args[0].split(/[, ]+/); } if (!args.length) { for (timer in tick.timers) { if (has.call(tick.timers, timer)) args.push(timer); } } for (i = 0, l = args.length; i < l; i++) { timer = tick.timers[args[i]]; if (!timer) continue; timer.clear(timer.timer); timer.fns = timer.timer = timer.clear = null; delete tick.timers[args[i]]; } return tick; }; /** * Adjust a timeout or interval to a new duration. * * @returns {Tick} * @api public */ Tick.prototype.adjust = function adjust(name, time) { var interval , tick = this , tock = ms(time) , timer = tick.timers[name]; if (!timer) return tick; interval = timer.clear === unsetInterval; timer.clear(timer.timer); timer.start = +(new Date()); timer.duration = tock; timer.timer = (interval ? setInterval : setTimeout)(tick.tock(name, !interval), tock); return tick; }; /** * We will no longer use this module, prepare your self for global cleanups. * * @returns {Boolean} * @api public */ Tick.prototype.end = Tick.prototype.destroy = function end() { if (!this.context) return false; this.clear(); this.context = this.timers = null; return true; }; // // Expose the timer factory. // Tick.Timer = Timer; module.exports = Tick;