From aa57797bf55f1b4bc383a5f8bdcbae19263788d2 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Fri, 20 Jan 2017 22:46:49 -0800 Subject: [PATCH 01/10] remove 0.12 support --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 64cb8da..7b94aa2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.12" - "4" - "6" + - "node" after_success: 'make report-coverage' From 0c968416f8b24b9f8fc5809b01b438acd274e5cd Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Fri, 20 Jan 2017 19:04:42 -0800 Subject: [PATCH 02/10] emit stop event. make stop faster by cancelling queued invocations. --- lib/index.js | 118 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 14 deletions(-) diff --git a/lib/index.js b/lib/index.js index 71d1e4c..070292e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -34,6 +34,7 @@ function Reissue(opts) { assert.func(opts.func, 'func'); assert.optionalObject(opts.context, 'context'); assert.optionalArray(opts.args, 'args'); + assert.optionalNumber(opts.timeout, 'timeout'); // assert options of different types var typeofInterval = typeof opts.interval; @@ -76,6 +77,12 @@ function Reissue(opts) { self._funcArgs = (opts.args) ? opts.args.concat(boundDone) : [boundDone]; + /** + * schedule an optional timeout which will trigger timeout event if a + * given invocation exceeds this number. + * @type {Number} + */ + self._timeoutMs = opts.timeout || null; //-------------------------------------------------------------------------- // internal properties @@ -108,6 +115,13 @@ function Reissue(opts) { * @type {Boolean} */ self._inUserFunc = false; + + /* + * setTimeout handler of an internal timeout implementation. used to fire + * the timeout event if a user supplied function takes too long. + * @type {Function} + */ + self._timeoutHandlerId = null; } util.inherits(Reissue, events.EventEmitter); @@ -134,6 +148,24 @@ Reissue.prototype._execute = function _execute() { self._inUserFunc = true; // execute their func self._func.apply(self._funcContext, self._funcArgs); + + // if timeout option is specified, schedule one here. basically, we + // execute the next invocation immediately above, then schedule a + // timeout handler, so we basically have two set timeout functions + // being scheduled. + if (self._timeoutMs !== null) { + // assign timeout to self so that we can cancel it if we complete + // on time. + self._timeoutHandlerId = setTimeout( + bind(self._onTimeout, self), + self._timeoutMs + ); + // set our own stateful flags on timeout handler + self._timeoutHandlerId.___reissue = { + fired: false, + done: false + }; + } } }; @@ -163,25 +195,50 @@ Reissue.prototype._done = function _done(err) { self.emit('error', err); } - // if user called stop(), we're done! don't queue up another invocation. - if (self._active === false) { - return; - } else { - // start invocation immediately if: the elapsedTime is greater than the - // interval, which means the last execution took longer than the - // interval itself. otherwise, subtract the time the previous - // invocation took. - var timeToInvocation = (elapsedTime >= interval) ? - 0 : (interval - elapsedTime); + // if a timeout was setup, and user supplied fn exceeded that timeout and + // we're still waiting for it to complete, block. + if (self._timeoutHandlerId) { + if (self._timeoutHandlerId.___reissue.fired === true && + self._timeoutHandlerId.___reissue.done === false) { + return self.once('_timeoutComplete', function() { + _internalDone(); + }); + } + } + // in every other case, we're fine, since we've finished before the + // timeout event has occurred. call _internalDone where we will clear + // the timeout event. + return _internalDone(); + + // common completion function called by forked code above + function _internalDone() { + + // clear any timeout handlers + if (self._timeoutHandlerId) { + clearTimeout(self._timeoutHandlerId); + self._timeoutHandlerId = null; + } - self._nextHandlerId = setTimeout(function _nextInvocation() { - self._execute(); - }, timeToInvocation); + // if user called stop() sometime during last invocation, we're done! + // don't queue up another invocation. + if (self._active === false) { + self._stop(); + } else { + // start invocation immediately if: the elapsedTime is greater than + // the interval, which means the last execution took longer than + // the interval itself. otherwise, subtract the time the previous + // invocation took. + var timeToInvocation = (elapsedTime >= interval) ? + 0 : (interval - elapsedTime); + + self._nextHandlerId = setTimeout(function _nextInvocation() { + self._execute(); + }, timeToInvocation); + } } }; - /** * internal implementation of stop. clears all timeout handlers and emits the * stop event. @@ -203,6 +260,39 @@ Reissue.prototype._stop = function _stop() { }; +/** + * called when the interval function "times out", or in other words takes + * longer than then specified timeout interval. this blocks the next invocation + * of the interval function until user calls the callback on the timeout + * event. + * @private + * @method _onTimeout + * @return {undefined} + */ +Reissue.prototype._onTimeout = function _onTimeout() { + + var self = this; + + self._timeoutHandlerId.___reissue.fired = true; + + function _internalOnTimeoutDone() { + self._timeoutHandlerId.___reissue.done = true; + self.emit('_timeoutComplete'); + } + + // if user is listening to timeout event, fire the event and block on its + // completion. + if (self.listenerCount('timeout') > 0) { + self.emit('timeout', function onTimeoutHandlerComplete() { + _internalOnTimeoutDone(); + }); + } + // otherwise, we're done + else { + _internalOnTimeoutDone(); + } +}; + //------------------------------------------------------------------------------ // public methods //------------------------------------------------------------------------------ From e495020d3f0ba344aecf67cfb47092cde14f7b6c Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Fri, 20 Jan 2017 20:14:58 -0800 Subject: [PATCH 03/10] support timeout event, and cancelling invocations via timeout event --- lib/index.js | 24 ++++++++++++++++---- test/index.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 070292e..0174be9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -138,11 +138,10 @@ util.inherits(Reissue, events.EventEmitter); * @return {undefined} */ Reissue.prototype._execute = function _execute() { + var self = this; self._startTime = Date.now(); - // this last invocation might have been scheduled before user called - // stop(). ensure we're still active before invocation. if (self._active === true) { // set flag so we know we're currently in user supplied func self._inUserFunc = true; @@ -166,6 +165,8 @@ Reissue.prototype._execute = function _execute() { done: false }; } + } else { + self._stop(); } }; @@ -273,6 +274,12 @@ Reissue.prototype._onTimeout = function _onTimeout() { var self = this; + // before we do anything, see if we got stopped. + if (self._active === false) { + self._stop(); + return; + } + self._timeoutHandlerId.___reissue.fired = true; function _internalOnTimeoutDone() { @@ -283,8 +290,15 @@ Reissue.prototype._onTimeout = function _onTimeout() { // if user is listening to timeout event, fire the event and block on its // completion. if (self.listenerCount('timeout') > 0) { - self.emit('timeout', function onTimeoutHandlerComplete() { + self.emit('timeout', function onTimeoutHandlerComplete(cancel) { _internalOnTimeoutDone(); + + // if cancel is true, clear the currently timed out invocation, + // schedule another one immediately. + if (cancel === true) { + clearTimeout(self._nextHandlerId); + self._execute(); + } }); } // otherwise, we're done @@ -323,7 +337,9 @@ Reissue.prototype.start = function start(delay) { self._execute(); }, delay); } else { - self._execute(); + self._nextHandlerId = setImmediate(function() { + self._execute(); + }); } }; diff --git a/test/index.js b/test/index.js index aa0d75f..036e87a 100644 --- a/test/index.js +++ b/test/index.js @@ -457,4 +457,65 @@ describe('Reissue module', function() { timer.stop(); }, 1000); }); + + + it('should stop and timeout should never fire', function(done) { + + var timer = reissue.create({ + func: function(callback) { + return setTimeout(callback, 500); + }, + interval: 100, + timeout: 200 + }); + + timer.on('timeout', function(callback) { + assert.fail('should not get here!'); + return callback(); + }); + + timer.on('stop', function() { + return done(); + }); + + timer.start(); + timer.stop(); + }); + + + it('should cancel next invocation via timeout callback', function(done) { + + var timer = reissue.create({ + func: function(callback) { + return setTimeout(callback, 500); + }, + interval: 100, + timeout: 200 + }); + + var start = Date.now(); + + timer.once('timeout', function(callback) { + timer.once('timeout', function(cb) { + // since we've thrown the first one away, and invoked it again + // immediately, we should be somewhere between 400-500ms + // elapsed + var elapsed = Date.now() - start; + assert.isAtLeast(elapsed, 400); + assert.isAtMost(elapsed, 500); + timer.stop(); + return cb(); + }); + + // true cancels the timed out invocation and schedules one + // immediately + return callback(true); + }); + + timer.on('stop', function() { + return done(); + }); + + timer.start(); + }); }); From 0e4c38797c097f55d24dae78a25eb1d69c61e2c7 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Mon, 23 Jan 2017 15:39:20 -0800 Subject: [PATCH 04/10] rearrange vars --- lib/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/index.js b/lib/index.js index 0174be9..7e7c2e7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -101,12 +101,6 @@ function Reissue(opts) { */ self._startTime = 0; - /** - * setTimeout handler of next invocation - * @type {Function} - */ - self._nextHandlerId = null; - /** * boolean flag set when we are waiting for user supplied function to * complete. technically we should know this if self._nextHandlerId had @@ -116,6 +110,12 @@ function Reissue(opts) { */ self._inUserFunc = false; + /** + * setTimeout handler of next invocation + * @type {Function} + */ + self._nextHandlerId = null; + /* * setTimeout handler of an internal timeout implementation. used to fire * the timeout event if a user supplied function takes too long. From 8534523fcc7b6bdbc12dc503a149dbd4878e39e4 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Mon, 23 Jan 2017 15:54:57 -0800 Subject: [PATCH 05/10] use setTimeout instead of setImmediate --- lib/index.js | 25 ++++++++++++++----------- test/index.js | 29 ++++------------------------- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/lib/index.js b/lib/index.js index 7e7c2e7..4d45c04 100644 --- a/lib/index.js +++ b/lib/index.js @@ -257,6 +257,12 @@ Reissue.prototype._stop = function _stop() { self._nextHandlerId = null; } + // clear any timeouts scheduled + if (self._timeoutHandlerId) { + clearTimeout(self._timeoutHandlerId); + self._timeoutHandlerId = null; + } + self.emit('stop'); }; @@ -320,9 +326,12 @@ Reissue.prototype._onTimeout = function _onTimeout() { * @return {undefined} */ Reissue.prototype.start = function start(delay) { - var self = this; + assert.optionalNumber(delay); + var self = this; + var realDelay = (typeof delay === 'number' && delay >= 0) ? delay : 0; + // before starting, see if reissue is already active. if so, throw an // error. if (self._active === true) { @@ -332,15 +341,9 @@ Reissue.prototype.start = function start(delay) { // set the flag and off we go! self._active = true; - if (typeof delay === 'number') { - self._nextHandlerId = setTimeout(function _nextInvocation() { - self._execute(); - }, delay); - } else { - self._nextHandlerId = setImmediate(function() { - self._execute(); - }); - } + self._nextHandlerId = setTimeout(function _nextInvocation() { + self._execute(); + }, realDelay); }; @@ -351,6 +354,7 @@ Reissue.prototype.start = function start(delay) { * @return {undefined} */ Reissue.prototype.stop = function stop() { + var self = this; // NOTE: while the below if statements could be collapsed to be more more @@ -364,7 +368,6 @@ Reissue.prototype.stop = function stop() { // here, we are either: // 1) queued up waiting for the next invocation // 2) waiting for user supplied function to complete - if (self._inUserFunc === false) { // case #1 // if we're just waiting for the next invocation, call stop now diff --git a/test/index.js b/test/index.js index 036e87a..383d50e 100644 --- a/test/index.js +++ b/test/index.js @@ -341,27 +341,6 @@ describe('Reissue module', function() { }); - it('should start asynchronously after explicit 0 delay', function(done) { - - var async = false; - - var timer = reissue.create({ - func: function(callback) { - - // if async === false, this was called synchronously - assert.equal(async, true); - return done(); - // no need to call reissue's callback here, as that will just - // invoke this function again and call done() which will fail - // the test. - }, - interval: 100 - }); - timer.start(0); - async = true; - }); - - it('should not execute first invocation if stop was called', function(done) { @@ -459,10 +438,12 @@ describe('Reissue module', function() { }); - it('should stop and timeout should never fire', function(done) { + it('should emit stop, first invocation and timeout should never fire', + function(done) { var timer = reissue.create({ func: function(callback) { + assert.fail('should not get here!'); return setTimeout(callback, 500); }, interval: 100, @@ -474,9 +455,7 @@ describe('Reissue module', function() { return callback(); }); - timer.on('stop', function() { - return done(); - }); + timer.on('stop', done); timer.start(); timer.stop(); From ed0f508217f596070a1acdad8e41aea09cb7d355 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Mon, 23 Jan 2017 19:05:42 -0800 Subject: [PATCH 06/10] make timeout simpler --- lib/index.js | 78 ++++++++++++----------------------------------- test/index.js | 84 +++++++++++++++++++++++++-------------------------- 2 files changed, 60 insertions(+), 102 deletions(-) diff --git a/lib/index.js b/lib/index.js index 4d45c04..9f76a47 100644 --- a/lib/index.js +++ b/lib/index.js @@ -140,33 +140,25 @@ util.inherits(Reissue, events.EventEmitter); Reissue.prototype._execute = function _execute() { var self = this; - self._startTime = Date.now(); - if (self._active === true) { - // set flag so we know we're currently in user supplied func - self._inUserFunc = true; - // execute their func - self._func.apply(self._funcContext, self._funcArgs); - - // if timeout option is specified, schedule one here. basically, we - // execute the next invocation immediately above, then schedule a - // timeout handler, so we basically have two set timeout functions - // being scheduled. - if (self._timeoutMs !== null) { - // assign timeout to self so that we can cancel it if we complete - // on time. - self._timeoutHandlerId = setTimeout( - bind(self._onTimeout, self), - self._timeoutMs - ); - // set our own stateful flags on timeout handler - self._timeoutHandlerId.___reissue = { - fired: false, - done: false - }; - } - } else { - self._stop(); + // start invocation timer + self._startTime = Date.now(); + // set flag so we know we're currently in user supplied func + self._inUserFunc = true; + // execute their func + self._func.apply(self._funcContext, self._funcArgs); + + // if timeout option is specified, schedule one here. basically, we + // execute the next invocation immediately above, then schedule a + // timeout handler, so we basically have two set timeout functions + // being scheduled. + if (self._timeoutMs !== null) { + // assign timeout to self so that we can cancel it if we complete + // on time. + self._timeoutHandlerId = setTimeout( + bind(self._onTimeout, self), + self._timeoutMs + ); } }; @@ -196,16 +188,6 @@ Reissue.prototype._done = function _done(err) { self.emit('error', err); } - // if a timeout was setup, and user supplied fn exceeded that timeout and - // we're still waiting for it to complete, block. - if (self._timeoutHandlerId) { - if (self._timeoutHandlerId.___reissue.fired === true && - self._timeoutHandlerId.___reissue.done === false) { - return self.once('_timeoutComplete', function() { - _internalDone(); - }); - } - } // in every other case, we're fine, since we've finished before the // timeout event has occurred. call _internalDone where we will clear // the timeout event. @@ -286,31 +268,9 @@ Reissue.prototype._onTimeout = function _onTimeout() { return; } - self._timeoutHandlerId.___reissue.fired = true; - - function _internalOnTimeoutDone() { - self._timeoutHandlerId.___reissue.done = true; - self.emit('_timeoutComplete'); - } - // if user is listening to timeout event, fire the event and block on its // completion. - if (self.listenerCount('timeout') > 0) { - self.emit('timeout', function onTimeoutHandlerComplete(cancel) { - _internalOnTimeoutDone(); - - // if cancel is true, clear the currently timed out invocation, - // schedule another one immediately. - if (cancel === true) { - clearTimeout(self._nextHandlerId); - self._execute(); - } - }); - } - // otherwise, we're done - else { - _internalOnTimeoutDone(); - } + self.emit('timeout'); }; //------------------------------------------------------------------------------ diff --git a/test/index.js b/test/index.js index 383d50e..cc54903 100644 --- a/test/index.js +++ b/test/index.js @@ -421,11 +421,6 @@ describe('Reissue module', function() { interval: 500 }); - timer.on('stop', function() { - assert.deepEqual(out, [0,1]); - return done(); - }); - timer.start(); // this should allow two invocations, calling stop while user supplied @@ -433,68 +428,71 @@ describe('Reissue module', function() { // completes, stop should get emitted and the third invocation is never // scheduled. setTimeout(function() { + timer.on('stop', function() { + assert.deepEqual(out, [0,1]); + return done(); + }); timer.stop(); }, 1000); }); - it('should emit stop, first invocation and timeout should never fire', - function(done) { + it('should emit timeout event', function(done) { + var callCount = 0; + var timeoutFired = false; var timer = reissue.create({ func: function(callback) { - assert.fail('should not get here!'); - return setTimeout(callback, 500); + callCount++; + return setTimeout(callback, 300); }, - interval: 100, - timeout: 200 + interval: 1000, + timeout: 150 }); - timer.on('timeout', function(callback) { - assert.fail('should not get here!'); - return callback(); - }); + timer.start(); - timer.on('stop', done); + timer.on('timeout', function() { + timeoutFired = true; + }); - timer.start(); - timer.stop(); + // first invocation should fire, take too long, and timeout event + // should fire. it should complete before next interval, and while we're waiting for it to + // complete (250ms) stop is called. timeout event should not fire. + setTimeout(function() { + timer.on('stop', function() { + assert.isTrue(timeoutFired); + assert.equal(callCount, 1); + return done(); + }); + timer.stop(); + }, 400); }); - it('should cancel next invocation via timeout callback', function(done) { + it('should stop during invocation, and timeout event should not fire', + function(done) { var timer = reissue.create({ func: function(callback) { - return setTimeout(callback, 500); + return setTimeout(callback, 250); }, - interval: 100, - timeout: 200 + interval: 200, + timeout: 400 }); + timer.start(); - var start = Date.now(); - - timer.once('timeout', function(callback) { - timer.once('timeout', function(cb) { - // since we've thrown the first one away, and invoked it again - // immediately, we should be somewhere between 400-500ms - // elapsed - var elapsed = Date.now() - start; - assert.isAtLeast(elapsed, 400); - assert.isAtMost(elapsed, 500); - timer.stop(); - return cb(); - }); - - // true cancels the timed out invocation and schedules one - // immediately - return callback(true); + timer.on('timeout', function(callback) { + assert.fail('should not get here!'); + return callback(); }); - timer.on('stop', function() { - return done(); - }); + timer.on('stop', done); - timer.start(); + // first invocation should fire, and while we're waiting for it to + // complete (250ms) stop is called. timeout event should not fire. + setTimeout(function() { + timer.stop(); + }, 100); }); }); From ae5c471e80c50a27c18b744d1bdf255be7a86a87 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Mon, 23 Jan 2017 19:23:34 -0800 Subject: [PATCH 07/10] clean up tests --- lib/index.js | 20 +++++++------------- test/index.js | 49 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/lib/index.js b/lib/index.js index 9f76a47..40c5531 100644 --- a/lib/index.js +++ b/lib/index.js @@ -239,12 +239,10 @@ Reissue.prototype._stop = function _stop() { self._nextHandlerId = null; } - // clear any timeouts scheduled - if (self._timeoutHandlerId) { - clearTimeout(self._timeoutHandlerId); - self._timeoutHandlerId = null; - } + // no need to clear timeout handlers, as they're already cleared + // in _done before we get here. + // emit stop, and we're done! self.emit('stop'); }; @@ -262,15 +260,11 @@ Reissue.prototype._onTimeout = function _onTimeout() { var self = this; - // before we do anything, see if we got stopped. - if (self._active === false) { - self._stop(); - return; + // we might have called stop during current invocation. emit timeout event + // only if we're still active. + if (self._active === true) { + self.emit('timeout'); } - - // if user is listening to timeout event, fire the event and block on its - // completion. - self.emit('timeout'); }; //------------------------------------------------------------------------------ diff --git a/test/index.js b/test/index.js index cc54903..b178481 100644 --- a/test/index.js +++ b/test/index.js @@ -377,10 +377,8 @@ describe('Reissue module', function() { it('should emit stop, cancelling next invocation', function(done) { - var out = []; var i = 0; - var timer = reissue.create({ func: function(callback) { out.push(i++); @@ -450,15 +448,14 @@ describe('Reissue module', function() { timeout: 150 }); - timer.start(); - timer.on('timeout', function() { timeoutFired = true; }); - // first invocation should fire, take too long, and timeout event - // should fire. it should complete before next interval, and while we're waiting for it to - // complete (250ms) stop is called. timeout event should not fire. + timer.start(); + + // call stop after first invocation completes but before next + // one is fired setTimeout(function() { timer.on('stop', function() { assert.isTrue(timeoutFired); @@ -473,25 +470,59 @@ describe('Reissue module', function() { it('should stop during invocation, and timeout event should not fire', function(done) { + var callCount = 0; + var timeoutFired = false; var timer = reissue.create({ func: function(callback) { + callCount++; return setTimeout(callback, 250); }, interval: 200, - timeout: 400 + timeout: 100 }); + + timer.on('timeout', function() { + timeoutFired = true; + }); + timer.start(); + // first invocation should fire, and while we're waiting for it, we + // call stop. we should not invoke it a second time, and should not + // fire the timeout event. + setTimeout(function() { + timer.on('stop', function() { + assert.isFalse(timeoutFired); + assert.equal(callCount, 1); + return done(); + }); + timer.stop(); + }, 0); + }); + + + it('call stop during invocation, and timeout fires before invocation ' + + 'completes', function(done) { + + var timer = reissue.create({ + func: function(callback) { + return setTimeout(callback, 250); + }, + interval: 200, + timeout: 400 + }); + timer.on('timeout', function(callback) { assert.fail('should not get here!'); return callback(); }); - timer.on('stop', done); + timer.start(); // first invocation should fire, and while we're waiting for it to // complete (250ms) stop is called. timeout event should not fire. setTimeout(function() { + timer.on('stop', done); timer.stop(); }, 100); }); From f7b95f52404525df4c7c699b12ed00065d2c849f Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Mon, 23 Jan 2017 20:42:25 -0800 Subject: [PATCH 08/10] change test timing to make it more reliable --- test/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index b178481..556f909 100644 --- a/test/index.js +++ b/test/index.js @@ -382,7 +382,7 @@ describe('Reissue module', function() { var timer = reissue.create({ func: function(callback) { out.push(i++); - return callback(); + return setTimeout(callback, 400); }, interval: 500 }); @@ -397,7 +397,7 @@ describe('Reissue module', function() { // this should allow two invocations, then cancel the third. setTimeout(function() { timer.stop(); - }, 1000); + }, 900); }); From c26b65b865a9f190958476850576be9b130fdd30 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Mon, 23 Jan 2017 21:12:09 -0800 Subject: [PATCH 09/10] update readme --- README.md | 19 ++++++++++++++----- lib/index.js | 3 +++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7bf3eae..723a04f 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,11 @@ reissue takes the following options to its `create()` method: * `opts.func` {Function} the function to execute. This function is invoked with a callback function as it's last parameter. -* `opts.interval` {Number | Function} the inteval in ms to execute the function, -or a function that returns an interval, allowing usage of a dynamic interval. +* `opts.interval` {Number | Function} the interval in ms to execute the +function, or a function that returns an interval, allowing usage of a dynamic +interval. +* `[opts.timeout]` {Number} an optional timeout in ms. if any invocation of the +the supplied func exceeds this timeout, the `timeout` event is fired. * `[opts.context]` {Context} an optional `this` context for the function. use this in lieu of native `bind()` if you are concerned about performance. reissue uses `apply()` under the hood to do context/arg binding. @@ -98,8 +101,14 @@ If your function returns an error to the callback, this event will be emitted. The subscribed function will receive an error as it's only parameter. ### handler.on('stop', function() {...}) -The stop event is emitted when reissue successfully completes any ongoing -function invocations and stops all future invocations. +When the `stop()` method is called, this event is emitted when when either the +current invocation is successfully completed, or when the next scheduled +invocation is successfully cancelled. If the current invocation is "stuck" in +the sense that the callback never returns, the stop event will never fire. + +### handler.on('timeout', function() {...}) +If a `timeout` value is specified, this event will be fired when any given +invocation of the function exceeds the specified value. ## Contributing @@ -127,6 +136,6 @@ make codestyle-fix ## License -Copyright (c) 2015 Alex Liu +Copyright (c) 2017 Alex Liu Licensed under the MIT license. diff --git a/lib/index.js b/lib/index.js index 40c5531..78a228c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -24,6 +24,9 @@ var bind = require('./bind'); * @param {Object} opts.func an the function to execute * @param {Object | Function} opts.interval the interval to execute at, or a * function that returns an interval. function allows for dynamic intervals. + * @param {Number} [opts.timeout] an optional timeout value that causes the + * `timeout` event to be fired when a given invocation exceeds this value. + * function that returns an interval. function allows for dynamic intervals. * @param {Object} [opts.context] the context to bind the function to * @param {Object} [opts.args] any arguments to pass to the function */ From ee5dfe6ffb9bea3226975db9f5f12abc094f272e Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Tue, 24 Jan 2017 11:20:06 -0800 Subject: [PATCH 10/10] pr comments --- README.md | 6 ++++-- lib/index.js | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 723a04f..84d620c 100644 --- a/README.md +++ b/README.md @@ -101,14 +101,16 @@ If your function returns an error to the callback, this event will be emitted. The subscribed function will receive an error as it's only parameter. ### handler.on('stop', function() {...}) -When the `stop()` method is called, this event is emitted when when either the +When the `stop()` method is called, this event is emitted when either the current invocation is successfully completed, or when the next scheduled invocation is successfully cancelled. If the current invocation is "stuck" in the sense that the callback never returns, the stop event will never fire. ### handler.on('timeout', function() {...}) If a `timeout` value is specified, this event will be fired when any given -invocation of the function exceeds the specified value. +invocation of the function exceeds the specified value. However, if your user +supplied function is synchronous, and never gives up the event loop, it is +possible that this event may never get fired. ## Contributing diff --git a/lib/index.js b/lib/index.js index 78a228c..c57a7f0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -148,8 +148,12 @@ Reissue.prototype._execute = function _execute() { self._startTime = Date.now(); // set flag so we know we're currently in user supplied func self._inUserFunc = true; - // execute their func - self._func.apply(self._funcContext, self._funcArgs); + // execute their func on a setImmediate, such that we can schedule the + // timeout first. to be clear though, user func could be sync and our + // timeout may never fire. + setImmediate(function _executeImmediately() { + self._func.apply(self._funcContext, self._funcArgs); + }); // if timeout option is specified, schedule one here. basically, we // execute the next invocation immediately above, then schedule a