Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade lolex with async versions of all timer-executing calls #105

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 176 additions & 25 deletions src/lolex-src.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ var keys = Object.keys || function (obj) {

exports.timers = timers;

var globalSetImmediate = global.setImmediate;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't fly without a polyfill library for most browsers. setImmediate is not an Ecmascript standard and is only found in Node and Microsoft's javascript engines. See this for recommended workarounds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purposes of this PR... would falling back to setTimeout be good enough?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominykas yes, I think so. if the performance implications bother people, they can supply a proper polyfill (there are lots of them) that uses fast versions (such as postMessage, ModificationObserver, etc)


function createClock(now, loopLimit) {
loopLimit = loopLimit || 1000;

Expand Down Expand Up @@ -473,54 +475,108 @@ function createClock(now, loopLimit) {
return clearTimer(clock, timerId, "Immediate");
};

clock.tick = function tick(ms) {
function doTick(ms, isAsync, resolve, reject) {
ms = typeof ms === "number" ? ms : parseTime(ms);
var tickFrom = clock.now;
var tickTo = clock.now + ms;
var previous = clock.now;
var timer = firstTimerInRange(clock, tickFrom, tickTo);
var oldNow, firstException;
var oldNow, firstException, nextPromiseTick, compensationCheck, postTimerCall;

clock.duringTick = true;

function updateHrTime(newNow) {
clock.hrNow += (newNow - clock.now);
}

while (timer && tickFrom <= tickTo) {
if (clock.timers[timer.id]) {
updateHrTime(timer.callAt);
tickFrom = timer.callAt;
clock.now = timer.callAt;
try {
function doTickInner() {
while (timer && tickFrom <= tickTo) {
if (clock.timers[timer.id]) {
updateHrTime(timer.callAt);
tickFrom = timer.callAt;
clock.now = timer.callAt;
oldNow = clock.now;
callTimer(clock, timer);
// compensate for any setSystemTime() call during timer callback
if (oldNow !== clock.now) {
tickFrom += clock.now - oldNow;
tickTo += clock.now - oldNow;
previous += clock.now - oldNow;

try {
callTimer(clock, timer);
} catch (e) {
firstException = firstException || e;
}

if (isAsync) {
// finish up after native setImmediate callback to allow
// all native es6 promises to process their callbacks after
// each timer fires.
globalSetImmediate(nextPromiseTick);
return;
}
} catch (e) {
firstException = firstException || e;

compensationCheck();
}

postTimerCall();
}

timer = firstTimerInRange(clock, previous, tickTo);
previous = tickFrom;
}
clock.duringTick = false;
updateHrTime(tickTo);
clock.now = tickTo;

clock.duringTick = false;
updateHrTime(tickTo);
clock.now = tickTo;
if (firstException) {
throw firstException;
}

if (firstException) {
throw firstException;
if (isAsync) {
resolve(clock.now);
} else {
return clock.now;
}
}

return clock.now;
nextPromiseTick = isAsync && function () {
try {
compensationCheck();
postTimerCall();
doTickInner();
} catch (e) {
reject(e);
}
};

compensationCheck = function () {
// compensate for any setSystemTime() call during timer callback
if (oldNow !== clock.now) {
tickFrom += clock.now - oldNow;
tickTo += clock.now - oldNow;
previous += clock.now - oldNow;
}
};

postTimerCall = function () {
timer = firstTimerInRange(clock, previous, tickTo);
previous = tickFrom;
};

return doTickInner();
}

clock.tick = function tick(ms) {
return doTick(ms, false);
};

if (typeof global.Promise !== "undefined") {
clock.tickAsync = function tickAsync(ms) {
return new global.Promise(function (resolve, reject) {
globalSetImmediate(function () {
try {
doTick(ms, true, resolve, reject);
} catch (e) {
reject(e);
}
});
});
};
}

clock.next = function next() {
var timer = firstTimer(clock);
if (!timer) {
Expand All @@ -537,6 +593,42 @@ function createClock(now, loopLimit) {
}
};

if (typeof global.Promise !== "undefined") {
clock.nextAsync = function nextAsync() {
return new global.Promise(function (resolve, reject) {
globalSetImmediate(function () {
try {
var timer = firstTimer(clock);
if (!timer) {
resolve(clock.now);
return;
}

var err;
clock.duringTick = true;
clock.now = timer.callAt;
try {
callTimer(clock, timer);
} catch (e) {
err = e;
}
clock.duringTick = false;

globalSetImmediate(function () {
if (err) {
reject(err);
} else {
resolve(clock.now);
}
});
} catch (e) {
reject(e);
}
});
});
};
}

clock.runAll = function runAll() {
var numTimers, i;
for (i = 0; i < clock.loopLimit; i++) {
Expand All @@ -555,6 +647,46 @@ function createClock(now, loopLimit) {
throw new Error("Aborting after running " + clock.loopLimit + " timers, assuming an infinite loop!");
};

if (typeof global.Promise !== "undefined") {
clock.runAllAsync = function runAllAsync() {
return new global.Promise(function (resolve, reject) {
var i = 0;
function doRun() {
globalSetImmediate(function () {
try {
var numTimers;
if (i < clock.loopLimit) {
if (!clock.timers) {
resolve(clock.now);
return;
}

numTimers = Object.keys(clock.timers).length;
if (numTimers === 0) {
resolve(clock.now);
return;
}

clock.next();

i++;

doRun();
return;
}

reject(new Error("Aborting after running " + clock.loopLimit
+ " timers, assuming an infinite loop!"));
} catch (e) {
reject(e);
}
});
}
doRun();
});
};
}

clock.runToLast = function runToLast() {
var timer = lastTimer(clock);
if (!timer) {
Expand All @@ -564,6 +696,25 @@ function createClock(now, loopLimit) {
return clock.tick(timer.callAt);
};

if (typeof global.Promise !== "undefined") {
clock.runToLastAsync = function runToLastAsync() {
return new global.Promise(function (resolve, reject) {
globalSetImmediate(function () {
try {
var timer = lastTimer(clock);
if (!timer) {
resolve(clock.now);
}

resolve(clock.tickAsync(timer.callAt));
} catch (e) {
reject(e);
}
});
});
};
}

clock.reset = function reset() {
clock.timers = {};
};
Expand Down
Loading