Skip to content

Commit

Permalink
Fix #10 - add d3.{timeout,interval}.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Feb 12, 2016
1 parent 2e0c0bc commit b317561
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 25 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ Restart a timer with the specified *callback* and optional *delay* and *time*. T

Stops this timer, preventing subsequent callbacks. This method has no effect if the timer has already stopped.

<a name="timerOnce" href="#timerOnce">#</a> d3.<b>timerOnce</b>(<i>callback</i>[, <i>delay</i>[, <i>time</i>]])

Like [timer](#timer), except the timer automatically [stops](#timer_stop) on its first callback.

<a name="timerFlush" href="#timerFlush">#</a> d3.<b>timerFlush</b>()

Immediately invoke any eligible timer callbacks. Note that zero-delay timers are normally first executed after one frame (~17ms). This can cause a brief flicker because the browser renders the page twice: once at the end of the first event loop, then again immediately on the first timer callback. By flushing the timer queue at the end of the first event loop, you can run any zero-delay timers immediately and avoid the flicker.

<a name="timeout" href="#timeout">#</a> d3.<b>timeout</b>(<i>callback</i>[, <i>delay</i>[, <i>time</i>]])

Like [timer](#timer), except the timer automatically [stops](#timer_stop) on its first callback. A suitable replacement for [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout) that is guaranteed to not run in the background. The *callback* is passed the elapsed time.

<a name="interval" href="#interval">#</a> d3.<b>interval</b>(<i>callback</i>[, <i>delay</i>[, <i>time</i>]])

Like [timer](#timer), except the *callback* is invoked only every *delay* milliseconds; if *delay* is not specified, this is equivalent to [timer](#timer). A suitable replacement for [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) that is guaranteed to not run in the background. The *callback* is passed the elapsed time.
9 changes: 8 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
export {
now,
timer,
timerOnce,
timerFlush
} from "./src/timer";

export {
default as timeout
} from "./src/timeout";

export {
default as interval
} from "./src/interval";
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "d3-timer",
"version": "0.3.2",
"version": "0.4.0",
"description": "An efficient queue capable of managing thousands of concurrent animations.",
"keywords": [
"d3",
Expand Down
12 changes: 12 additions & 0 deletions src/interval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Timer, now} from "./timer";

export default function(callback, delay, time) {
var t = new Timer, d = delay;
if (delay == null) return t.restart(callback, delay, time), t;
delay = +delay, time = time == null ? now() : +time;
t.restart(function tick(elapsed) {
t.restart(tick, d += delay, time);
callback(elapsed - delay + d);
}, d, time);
return t;
}
11 changes: 11 additions & 0 deletions src/timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Timer} from "./timer";

export default function(callback, delay, time) {
var t = new Timer;
delay = delay == null ? 0 : +delay;
t.restart(function(elapsed) {
t.stop();
callback(elapsed + delay);
}, delay, time);
return t;
}
8 changes: 1 addition & 7 deletions src/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function clearNow() {
clockNow = 0;
}

function Timer() {
export function Timer() {
this._call =
this._time =
this._next = null;
Expand Down Expand Up @@ -52,12 +52,6 @@ export function timer(callback, delay, time) {
return t;
}

export function timerOnce(callback, delay, time) {
var t = new Timer;
t.restart(function(elapsed) { t.stop(); callback(elapsed); }, delay, time);
return t;
}

export function timerFlush() {
now(); // Get the current time, if not already set.
++frame; // Pretend we’ve set an alarm, if we haven’t already.
Expand Down
76 changes: 76 additions & 0 deletions test/interval-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
var tape = require("tape"),
timer = require("../"),
end = require("./end");

require("./inRange");

// It’s difficult to test the timing behavior reliably, since there can be small
// hiccups that cause a timer to be delayed. So we test only the mean rate.
tape("interval(callback) invokes the callback about every 17ms", function(test) {
var then = timer.now(), count = 0;
var t = timer.interval(function() {
if (++count > 10) {
t.stop();
test.inRange(timer.now() - then, (17 - 5) * count, (17 + 5) * count);
end(test);
}
});
});

tape("interval(callback) invokes the callback until the timer is stopped", function(test) {
var count = 0;
var t = timer.interval(function() {
if (++count > 2) {
t.stop();
end(test);
}
});
});

tape("interval(callback, delay) invokes the callback about every delay milliseconds", function(test) {
var then = timer.now(), delay = 50, count = 0;
var t = timer.interval(function() {
if (++count > 10) {
t.stop();
test.inRange(timer.now() - then, (delay - 5) * count, (delay + 5) * count);
end(test);
}
}, delay);
});

tape("interval(callback, delay, time) invokes the callback repeatedly after the specified delay relative to the given time", function(test) {
var then = timer.now() + 50, delay = 50;
var t = timer.interval(function(elapsed) {
test.inRange(timer.now() - then, delay - 10, delay + 10);
t.stop();
end(test);
}, delay, then);
});

tape("interval(callback) uses the global context for the callback", function(test) {
var t = timer.interval(function() {
test.equal(this, global);
t.stop();
end(test);
});
});

tape("interval(callback) passes the callback the elapsed time", function(test) {
var then = timer.now(), count = 0;
var t = timer.interval(function(elapsed) {
test.equal(elapsed, timer.now() - then);
t.stop();
end(test);
}, 100);
});

tape("interval(callback) returns a timer", function(test) {
var count = 0;
var t = timer.interval(function() { ++count; });
test.equal(t instanceof timer.timer, true);
t.stop();
setTimeout(function() {
test.equal(count, 0);
end(test);
}, 100);
});
24 changes: 12 additions & 12 deletions test/timerOnce-test.js → test/timeout-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,48 @@ var tape = require("tape"),

require("./inRange");

tape("timerOnce(callback) invokes the callback once", function(test) {
tape("timeout(callback) invokes the callback once", function(test) {
var count = 0;
timer.timerOnce(function() {
timer.timeout(function() {
test.equal(++count, 1);
end(test);
});
});

tape("timerOnce(callback, delay) invokes the callback once after the specified delay", function(test) {
tape("timeout(callback, delay) invokes the callback once after the specified delay", function(test) {
var then = timer.now(), delay = 50;
timer.timerOnce(function(elapsed) {
timer.timeout(function(elapsed) {
test.inRange(timer.now() - then, delay - 10, delay + 10);
end(test);
}, delay);
});

tape("timerOnce(callback, delay, time) invokes the callback once after the specified delay relative to the given time", function(test) {
tape("timeout(callback, delay, time) invokes the callback once after the specified delay relative to the given time", function(test) {
var then = timer.now() + 50, delay = 50;
timer.timerOnce(function(elapsed) {
timer.timeout(function(elapsed) {
test.inRange(timer.now() - then, delay - 10, delay + 10);
end(test);
}, delay, then);
});

tape("timerOnce(callback) uses the global context for the callback", function(test) {
timer.timerOnce(function() {
tape("timeout(callback) uses the global context for the callback", function(test) {
timer.timeout(function() {
test.equal(this, global);
end(test);
});
});

tape("timerOnce(callback) passes the callback the elapsed time", function(test) {
tape("timeout(callback) passes the callback the elapsed time", function(test) {
var then = timer.now(), count = 0;
timer.timerOnce(function(elapsed) {
timer.timeout(function(elapsed) {
test.equal(elapsed, timer.now() - then);
end(test);
});
});

tape("timerOnce(callback) returns a timer", function(test) {
tape("timeout(callback) returns a timer", function(test) {
var count = 0;
var t = timer.timerOnce(function() { ++count; });
var t = timer.timeout(function() { ++count; });
test.equal(t instanceof timer.timer, true);
t.stop();
setTimeout(function() {
Expand Down

0 comments on commit b317561

Please sign in to comment.