As described in issue #655, the handler of an interval cannot successfully clear the same interval that generated it's invocation. Solve this issue by changing the order in which interval's handlers are called and then rescheduled to: first reschedule it and then call it. The actual order (call first then reschedule) produces that, during the execution of the interval's handler, the handler is not registered as a function to run after a timeout or interval ("scheduledFunctions"), because it was previously unregistered. Consequently, if the handler calls clearInterval, that function wont be able to find the handler and remove it completely.
147 lines
3.9 KiB
JavaScript
147 lines
3.9 KiB
JavaScript
getJasmineRequireObj().DelayedFunctionScheduler = function() {
|
|
function DelayedFunctionScheduler() {
|
|
var self = this;
|
|
var scheduledLookup = [];
|
|
var scheduledFunctions = {};
|
|
var currentTime = 0;
|
|
var delayedFnCount = 0;
|
|
|
|
self.tick = function(millis) {
|
|
millis = millis || 0;
|
|
var endTime = currentTime + millis;
|
|
|
|
runScheduledFunctions(endTime);
|
|
currentTime = endTime;
|
|
};
|
|
|
|
self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
|
|
var f;
|
|
if (typeof(funcToCall) === 'string') {
|
|
/* jshint evil: true */
|
|
f = function() { return eval(funcToCall); };
|
|
/* jshint evil: false */
|
|
} else {
|
|
f = funcToCall;
|
|
}
|
|
|
|
millis = millis || 0;
|
|
timeoutKey = timeoutKey || ++delayedFnCount;
|
|
runAtMillis = runAtMillis || (currentTime + millis);
|
|
|
|
var funcToSchedule = {
|
|
runAtMillis: runAtMillis,
|
|
funcToCall: f,
|
|
recurring: recurring,
|
|
params: params,
|
|
timeoutKey: timeoutKey,
|
|
millis: millis
|
|
};
|
|
|
|
if (runAtMillis in scheduledFunctions) {
|
|
scheduledFunctions[runAtMillis].push(funcToSchedule);
|
|
} else {
|
|
scheduledFunctions[runAtMillis] = [funcToSchedule];
|
|
scheduledLookup.push(runAtMillis);
|
|
scheduledLookup.sort(function (a, b) {
|
|
return a - b;
|
|
});
|
|
}
|
|
|
|
return timeoutKey;
|
|
};
|
|
|
|
self.removeFunctionWithId = function(timeoutKey) {
|
|
for (var runAtMillis in scheduledFunctions) {
|
|
var funcs = scheduledFunctions[runAtMillis];
|
|
var i = indexOfFirstToPass(funcs, function (func) {
|
|
return func.timeoutKey === timeoutKey;
|
|
});
|
|
|
|
if (i > -1) {
|
|
if (funcs.length === 1) {
|
|
delete scheduledFunctions[runAtMillis];
|
|
deleteFromLookup(runAtMillis);
|
|
} else {
|
|
funcs.splice(i, 1);
|
|
}
|
|
|
|
// intervals get rescheduled when executed, so there's never more
|
|
// than a single scheduled function with a given timeoutKey
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
self.reset = function() {
|
|
currentTime = 0;
|
|
scheduledLookup = [];
|
|
scheduledFunctions = {};
|
|
delayedFnCount = 0;
|
|
};
|
|
|
|
return self;
|
|
|
|
function indexOfFirstToPass(array, testFn) {
|
|
var index = -1;
|
|
|
|
for (var i = 0; i < array.length; ++i) {
|
|
if (testFn(array[i])) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
function deleteFromLookup(key) {
|
|
var value = Number(key);
|
|
var i = indexOfFirstToPass(scheduledLookup, function (millis) {
|
|
return millis === value;
|
|
});
|
|
|
|
if (i > -1) {
|
|
scheduledLookup.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
function reschedule(scheduledFn) {
|
|
self.scheduleFunction(scheduledFn.funcToCall,
|
|
scheduledFn.millis,
|
|
scheduledFn.params,
|
|
true,
|
|
scheduledFn.timeoutKey,
|
|
scheduledFn.runAtMillis + scheduledFn.millis);
|
|
}
|
|
|
|
function runScheduledFunctions(endTime) {
|
|
if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
|
|
return;
|
|
}
|
|
|
|
do {
|
|
currentTime = scheduledLookup.shift();
|
|
|
|
var funcsToRun = scheduledFunctions[currentTime];
|
|
delete scheduledFunctions[currentTime];
|
|
|
|
for (var i = 0; i < funcsToRun.length; ++i) {
|
|
var funcToRun = funcsToRun[i];
|
|
|
|
if (funcToRun.recurring) {
|
|
reschedule(funcToRun);
|
|
}
|
|
|
|
funcToRun.funcToCall.apply(null, funcToRun.params || []);
|
|
}
|
|
} while (scheduledLookup.length > 0 &&
|
|
// checking first if we're out of time prevents setTimeout(0)
|
|
// scheduled in a funcToRun from forcing an extra iteration
|
|
currentTime !== endTime &&
|
|
scheduledLookup[0] <= endTime);
|
|
}
|
|
}
|
|
|
|
return DelayedFunctionScheduler;
|
|
};
|