From b317561f20b83839fd2a30e0b73f7ccd749b3727 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 12 Feb 2016 14:57:47 -0800 Subject: [PATCH] Fix #10 - add d3.{timeout,interval}. --- README.md | 12 ++-- index.js | 9 ++- package.json | 2 +- src/interval.js | 12 ++++ src/timeout.js | 11 +++ src/timer.js | 8 +-- test/interval-test.js | 76 +++++++++++++++++++++ test/{timerOnce-test.js => timeout-test.js} | 24 +++---- 8 files changed, 129 insertions(+), 25 deletions(-) create mode 100644 src/interval.js create mode 100644 src/timeout.js create mode 100644 test/interval-test.js rename test/{timerOnce-test.js => timeout-test.js} (50%) diff --git a/README.md b/README.md index e7420a3..f359b98 100644 --- a/README.md +++ b/README.md @@ -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. -# d3.timerOnce(callback[, delay[, time]]) - -Like [timer](#timer), except the timer automatically [stops](#timer_stop) on its first callback. - # d3.timerFlush() 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. + +# d3.timeout(callback[, delay[, time]]) + +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. + +# d3.interval(callback[, delay[, time]]) + +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. diff --git a/index.js b/index.js index 50b28b1..247fe90 100644 --- a/index.js +++ b/index.js @@ -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"; diff --git a/package.json b/package.json index 132ba91..370c1d1 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/interval.js b/src/interval.js new file mode 100644 index 0000000..2f73916 --- /dev/null +++ b/src/interval.js @@ -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; +} diff --git a/src/timeout.js b/src/timeout.js new file mode 100644 index 0000000..6357034 --- /dev/null +++ b/src/timeout.js @@ -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; +} diff --git a/src/timer.js b/src/timer.js index ceccd19..1fee199 100644 --- a/src/timer.js +++ b/src/timer.js @@ -18,7 +18,7 @@ function clearNow() { clockNow = 0; } -function Timer() { +export function Timer() { this._call = this._time = this._next = null; @@ -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. diff --git a/test/interval-test.js b/test/interval-test.js new file mode 100644 index 0000000..7080729 --- /dev/null +++ b/test/interval-test.js @@ -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); +}); diff --git a/test/timerOnce-test.js b/test/timeout-test.js similarity index 50% rename from test/timerOnce-test.js rename to test/timeout-test.js index 99cc957..1876636 100644 --- a/test/timerOnce-test.js +++ b/test/timeout-test.js @@ -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() {