Skip to content

Commit 857e8bc

Browse files
committed
Fix #135 and Cleanup Timers
Cleanup timers to make clear what is going on. Simplify based on the fact we are inserting into the _timers in sorted order. Remove multiple sources of truth. Simplify stale timer handling (should we move this check in other spots like run)? Remove duplicate test of hanging issue, the other one with the spin is more reliable.
1 parent 741c440 commit 857e8bc

File tree

6 files changed

+101
-123
lines changed

6 files changed

+101
-123
lines changed

.jshintrc

+6-23
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,11 @@
11
{
22
"predef": [
3-
"define",
4-
"console",
5-
"require",
6-
"requireModule",
7-
"equal",
8-
"notEqual",
9-
"notStrictEqual",
10-
"test",
11-
"asyncTest",
12-
"testBoth",
13-
"testWithDefault",
14-
"raises",
15-
"throws",
16-
"deepEqual",
17-
"start",
18-
"stop",
19-
"ok",
20-
"strictEqual",
21-
"module",
22-
"expect",
23-
"process"
3+
"setTimeout", "clearTimeout"
244
],
5+
"esnext": true,
256
"node" : false,
26-
"browser" : true,
7+
"browser" : false,
8+
279
"boss" : true,
2810
"curly": false,
2911
"debug": false,
@@ -46,5 +28,6 @@
4628
"strict": false,
4729
"white": false,
4830
"eqnull": true,
49-
"esnext": true
31+
"trailing": true,
32+
"unused": "vars"
5033
}

lib/backburner.js

+93-61
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,23 @@ export default function Backburner(queueNames, options) {
2525
this.instanceStack = [];
2626
this._debouncees = [];
2727
this._throttlers = [];
28-
this._timers = [];
2928
this._eventCallbacks = {
3029
end: [],
3130
begin: []
3231
};
32+
33+
this._timerTimeoutId = undefined;
34+
this._timers = [];
35+
36+
var _this = this;
37+
this._boundRunExpiredTimers = function () {
38+
_this._runExpiredTimers();
39+
};
3340
}
3441

42+
// ms of delay before we conclude a timeout was lost
43+
var TIMEOUT_STALLED_THRESHOLD = 1000;
44+
3545
Backburner.prototype = {
3646
begin: function() {
3747
var options = this.options;
@@ -359,12 +369,27 @@ Backburner.prototype = {
359369
}
360370
}
361371

372+
return this._setTimeout(fn, executeAt);
373+
},
374+
375+
_setTimeout(fn, executeAt) {
376+
if (this._timers.length === 0) {
377+
this._timers.push(executeAt, fn);
378+
this._installTimerTimeout();
379+
return fn;
380+
}
381+
382+
this._reinstallStalledTimerTimeout();
383+
362384
// find position to insert
363385
var i = searchTimer(executeAt, this._timers);
364386

365387
this._timers.splice(i, 0, executeAt, fn);
366388

367-
updateLaterTimer(this, executeAt, wait);
389+
// we should be the new earliest timer if i == 0
390+
if (i === 0) {
391+
this._reinstallTimerTimeout();
392+
}
368393

369394
return fn;
370395
},
@@ -464,20 +489,17 @@ Backburner.prototype = {
464489
},
465490

466491
cancelTimers: function() {
467-
var clearItems = function(item) {
492+
function clearItems(item) {
468493
clearTimeout(item[2]);
469-
};
494+
}
470495

471496
each(this._throttlers, clearItems);
472497
this._throttlers = [];
473498

474499
each(this._debouncees, clearItems);
475500
this._debouncees = [];
476501

477-
if (this._laterTimer) {
478-
clearTimeout(this._laterTimer);
479-
this._laterTimer = null;
480-
}
502+
this._clearTimerTimeout();
481503
this._timers = [];
482504

483505
if (this._autorun) {
@@ -490,7 +512,7 @@ Backburner.prototype = {
490512
return !!this._timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun;
491513
},
492514

493-
cancel: function(timer) {
515+
cancel: function (timer) {
494516
var timerType = typeof timer;
495517

496518
if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
@@ -500,13 +522,7 @@ Backburner.prototype = {
500522
if (this._timers[i + 1] === timer) {
501523
this._timers.splice(i, 2); // remove the two elements
502524
if (i === 0) {
503-
if (this._laterTimer) { // Active timer? Then clear timer and reset for future timer
504-
clearTimeout(this._laterTimer);
505-
this._laterTimer = null;
506-
}
507-
if (this._timers.length > 0) { // Update to next available timer when available
508-
updateLaterTimer(this, this._timers[0], this._timers[0] - now());
509-
}
525+
this._reinstallTimerTimeout();
510526
}
511527
return true;
512528
}
@@ -538,6 +554,67 @@ Backburner.prototype = {
538554
}
539555

540556
return false;
557+
},
558+
559+
_runExpiredTimers: function () {
560+
this._timerTimeoutId = undefined;
561+
this.run(this, this._scheduleExpiredTimers);
562+
},
563+
564+
_scheduleExpiredTimers: function () {
565+
var n = now();
566+
var timers = this._timers;
567+
var i = 0;
568+
var l = timers.length;
569+
for (; i < l; i += 2) {
570+
var executeAt = timers[i];
571+
var fn = timers[i+1];
572+
if (executeAt <= n) {
573+
this.schedule(this.options.defaultQueue, null, fn);
574+
} else {
575+
break;
576+
}
577+
}
578+
timers.splice(0, i);
579+
this._installTimerTimeout();
580+
},
581+
582+
_reinstallStalledTimerTimeout: function () {
583+
if (!this._timerTimeoutId) {
584+
return;
585+
}
586+
// if we have a timer we should always have a this._timerTimeoutId
587+
var minExpiresAt = this._timers[0];
588+
var delay = now() - minExpiresAt;
589+
// threshold of a second before we assume that the currently
590+
// installed timeout will not run, so we don't constantly reinstall
591+
// timeouts that are delayed but good still
592+
if (delay < TIMEOUT_STALLED_THRESHOLD) {
593+
return;
594+
}
595+
},
596+
597+
_reinstallTimerTimeout: function () {
598+
this._clearTimerTimeout();
599+
this._installTimerTimeout();
600+
},
601+
602+
_clearTimerTimeout: function () {
603+
if (!this._timerTimeoutId) {
604+
return;
605+
}
606+
clearTimeout(this._timerTimeoutId);
607+
this._timerTimeoutId = undefined;
608+
},
609+
610+
_installTimerTimeout: function () {
611+
if (!this._timers.length) {
612+
return;
613+
}
614+
var minExpiresAt = this._timers[0];
615+
var n = now();
616+
var wait = Math.max(0, minExpiresAt - n);
617+
this._timerTimeoutId = setTimeout(this._boundRunExpiredTimers, wait);
541618
}
542619
};
543620

@@ -565,51 +642,6 @@ function createAutorun(backburner) {
565642
});
566643
}
567644

568-
function updateLaterTimer(backburner, executeAt, wait) {
569-
var n = now();
570-
if (!backburner._laterTimer || executeAt < backburner._laterTimerExpiresAt || backburner._laterTimerExpiresAt < n) {
571-
572-
if (backburner._laterTimer) {
573-
// Clear when:
574-
// - Already expired
575-
// - New timer is earlier
576-
clearTimeout(backburner._laterTimer);
577-
578-
if (backburner._laterTimerExpiresAt < n) { // If timer was never triggered
579-
// Calculate the left-over wait-time
580-
wait = Math.max(0, executeAt - n);
581-
}
582-
}
583-
584-
backburner._laterTimer = platform.setTimeout(function() {
585-
backburner._laterTimer = null;
586-
backburner._laterTimerExpiresAt = null;
587-
executeTimers(backburner);
588-
}, wait);
589-
590-
backburner._laterTimerExpiresAt = n + wait;
591-
}
592-
}
593-
594-
function executeTimers(backburner) {
595-
var n = now();
596-
var fns, i, l;
597-
598-
backburner.run(function() {
599-
i = searchTimer(n, backburner._timers);
600-
601-
fns = backburner._timers.splice(0, i);
602-
603-
for (i = 1, l = fns.length; i < l; i += 2) {
604-
backburner.schedule(backburner.options.defaultQueue, null, fns[i]);
605-
}
606-
});
607-
608-
if (backburner._timers.length) {
609-
updateLaterTimer(backburner, backburner._timers[0], backburner._timers[0] - n);
610-
}
611-
}
612-
613645
function findDebouncee(target, method, debouncees) {
614646
return findItem(target, method, debouncees);
615647
}

lib/backburner.umd

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* globals module */
12
import Backburner from './backburner';
23
import platform from './backburner/platform';
34

lib/backburner/deferred-action-queues.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ DeferredActionQueues.prototype = {
4545
flush: function() {
4646
var queues = this.queues;
4747
var queueNames = this.queueNames;
48-
var queueName, queue, queueItems, priorQueueNameIndex;
48+
var queueName, queue;
4949
var queueNameIndex = 0;
5050
var numberOfQueues = queueNames.length;
51-
var options = this.options;
5251

5352
while (queueNameIndex < numberOfQueues) {
5453
queueName = queueNames[queueNameIndex];

lib/backburner/queue.js

-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ Queue.prototype = {
8080
},
8181

8282
pushUnique: function(target, method, args, stack) {
83-
var queue = this._queue, currentTarget, currentMethod, i, l;
8483
var KEY = this.globalOptions.GUID_KEY;
8584

8685
if (target && KEY) {

tests/set-timeout-test.js

-36
Original file line numberDiff line numberDiff line change
@@ -260,42 +260,6 @@ test('setTimeout doesn\'t trigger twice with earlier setTimeout', function() {
260260
}, 100);
261261
});
262262

263-
test('setTimeout doesn\'t hang when timeout is unfulfilled', function() { // See issue #86
264-
expect(3);
265-
266-
var bb = new Backburner(['one']);
267-
var called1 = 0;
268-
var called2 = 0;
269-
var calls = 0;
270-
var oldRun = bb.run;
271-
272-
// Count run() calls and relay them to original function
273-
bb.run = function () {
274-
calls++;
275-
oldRun.apply(bb, arguments);
276-
};
277-
278-
bb.setTimeout(function() {
279-
called1++;
280-
}, 0);
281-
282-
// Manually pass time and clear timeout
283-
clearTimeout(bb._laterTimer);
284-
bb._laterTimerExpiresAt = +(new Date()) - 5000;
285-
286-
bb.setTimeout(function() {
287-
called2++;
288-
}, 10);
289-
290-
stop();
291-
setTimeout(function () {
292-
start();
293-
equal(called1, 1, 'timeout 1 was called once');
294-
equal(called2, 1, 'timeout 2 was called once');
295-
equal(calls, 1, 'run() was called once'); // both at once
296-
}, 50);
297-
});
298-
299263
test('setTimeout with two Backburner instances', function() {
300264
expect(8);
301265

0 commit comments

Comments
 (0)