Skip to content

Commit

Permalink
emit stop event. make stop faster by cancelling queued invocations.
Browse files Browse the repository at this point in the history
  • Loading branch information
DonutEspresso committed Jan 21, 2017
1 parent 5143b2a commit a77f3c6
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 12 deletions.
82 changes: 70 additions & 12 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ function Reissue(opts) {
* @type {Number}
*/
self._startTime = 0;

/**
* setTimeout handler of next invocation
* @type {Function}
*/
self._nextHandlerId = null;
}
util.inherits(Reissue, events.EventEmitter);

Expand All @@ -115,6 +121,9 @@ Reissue.prototype._execute = function _execute() {
// 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;
// execute their func
self._func.apply(self._funcContext, self._funcArgs);
}
};
Expand All @@ -135,6 +144,11 @@ Reissue.prototype._done = function _done(err) {
var interval = self._interval();
var elapsedTime = Date.now() - self._startTime;

// we're out of user supplied func now
self._inUserFunc = false;
// clear out the handler id
self._nextHandlerId = null;

// re-emit error
if (err) {
self.emit('error', err);
Expand All @@ -143,20 +157,40 @@ Reissue.prototype._done = function _done(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);

self._nextHandlerId = setTimeout(function _nextInvocation() {
self._execute();
}, timeToInvocation);
}
};

// 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);

// assign the handler to ensure that consumers can't call start() again
// while we're waiting for the next invocation.
setTimeout(function _nextInvocation() {
self._execute();
}, timeToInvocation);

/**
* internal implementation of stop. clears all timeout handlers and emits the
* stop event.
* @private
* @method _stop
* @returns {undefined}
*/
Reissue.prototype._stop = function _stop() {

var self = this;

// clear the next invocation if one exists
if (self._nextHandlerId) {
clearTimeout(self._nextHandlerId);
self._nextHandlerId = null;
}

self.emit('stop');
};


Expand Down Expand Up @@ -203,7 +237,31 @@ Reissue.prototype.start = function start(delay) {
*/
Reissue.prototype.stop = function stop() {
var self = this;
self._active = false;

// NOTE: while the below if statements could be collapsed to be more more
// terse, this logic is easier to read in terms of maintainability.

// check if we are currently active. if not, we can stop now.
if (self._active === false) {
self._stop();
} else {
// in the else case, we are still active. there are two possibilities
// 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
// which will clear out the next invocation.
self._stop();
} else {
// case #2
// set active flag to false, when we come back from user function
// we will check this flag and call internal _stop()
self._active = false;
}
}
};


Expand Down
74 changes: 74 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,78 @@ describe('Reissue module', function() {
return done();
}, 500);
});


it('should emit stop event if reissue is inactive', function(done) {
var timer = reissue.create({
func: function(callback) {
return callback();
},
interval: 100
});
timer.on('stop', done);
timer.stop();
});


it('should emit stop, cancelling next invocation', function(done) {

var out = [];
var i = 0;

var timer = reissue.create({
func: function(callback) {
out.push(i++);
return callback();
},
interval: 500
});

timer.on('stop', function() {
assert.deepEqual(out, [0,1]);
return done();
});

timer.start();

// this should allow two invocations, then cancel the third.
setTimeout(function() {
timer.stop();
}, 1000);
});


it('should emit stop, never schedules next invocation', function(done) {

var out = [];
var i = 0;

var timer = reissue.create({
func: function(callback) {
out.push(i++);

if (i === 1) {
return setTimeout(callback, 600);
} else {
return callback();
}
},
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
// function is still going on the second time around. once it
// completes, stop should get emitted and the third invocation is never
// scheduled.
setTimeout(function() {
timer.stop();
}, 1000);
});
});

0 comments on commit a77f3c6

Please sign in to comment.