Add a RestartableTimer class.
This is useful for heartbeat-style timeouts where a timeout is reset
when certain actions occur.
R=lrn@google.com
Review URL: https://codereview.chromium.org//1417373004 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f9ec48..5801872 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@
- Added `AsyncMemoizer.future`, which allows the result to be accessed before
`runOnce()` is called.
+- Added `RestartableTimer`, a non-periodic timer that can be reset over and
+ over.
+
## 1.3.0
- Added `StreamCompleter` class for creating a stream now and providing its
diff --git a/lib/async.dart b/lib/async.dart
index 07b418b..9da3552 100644
--- a/lib/async.dart
+++ b/lib/async.dart
@@ -14,6 +14,7 @@
export "src/delegate/stream_sink.dart";
export "src/delegate/stream_subscription.dart";
export "src/future_group.dart";
+export "src/restartable_timer.dart";
export "src/result_future.dart";
export "src/stream_completer.dart";
export "src/stream_group.dart";
diff --git a/lib/src/restartable_timer.dart b/lib/src/restartable_timer.dart
new file mode 100644
index 0000000..05196d2
--- /dev/null
+++ b/lib/src/restartable_timer.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library async.restartable_timer;
+
+import 'dart:async';
+
+/// A non-periodic timer that can be restarted any number of times.
+///
+/// Once restarted (via [reset]), the timer counts down from its original
+/// duration again.
+class RestartableTimer implements Timer {
+ /// The duration of the timer.
+ final Duration _duration;
+
+ /// The callback to call when the timer fires.
+ final ZoneCallback _callback;
+
+ /// The timer for the current or most recent countdown.
+ ///
+ /// This timer is canceled and overwritten every time this [RestartableTimer]
+ /// is reset.
+ Timer _timer;
+
+ /// Creates a new timer.
+ ///
+ /// The [callback] function is invoked after the given [duration]. Unlike a
+ /// normal non-periodic [Timer], [callback] may be called more than once.
+ RestartableTimer(this._duration, this._callback) {
+ _timer = new Timer(_duration, _callback);
+ }
+
+ bool get isActive => _timer.isActive;
+
+ /// Restarts the timer so that it counts down from its original duration
+ /// again.
+ ///
+ /// This restarts the timer even if it has already fired or has been canceled.
+ void reset() {
+ _timer.cancel();
+ _timer = new Timer(_duration, _callback);
+ }
+
+ void cancel() {
+ _timer.cancel();
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 7062a99..91d752d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,9 +1,10 @@
name: async
-version: 1.4.0-dev
+version: 1.4.0
author: Dart Team <misc@dartlang.org>
description: Utility functions and classes related to the 'dart:async' library.
homepage: https://www.github.com/dart-lang/async
dev_dependencies:
+ fake_async: "^0.1.2"
stack_trace: "^1.0.0"
test: "^0.12.0"
environment:
diff --git a/test/restartable_timer_test.dart b/test/restartable_timer_test.dart
new file mode 100644
index 0000000..6732b81
--- /dev/null
+++ b/test/restartable_timer_test.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+main() {
+ test("runs the callback once the duration has elapsed", () {
+ new FakeAsync().run((async) {
+ var fired = false;
+ var timer = new RestartableTimer(new Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+
+ async.elapse(new Duration(seconds: 1));
+ expect(fired, isTrue);
+ });
+ });
+
+ test("doesn't run the callback if the timer is canceled", () {
+ new FakeAsync().run((async) {
+ var fired = false;
+ var timer = new RestartableTimer(new Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.cancel();
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+ });
+ });
+
+ test("resets the duration if the timer is reset before it fires", () {
+ new FakeAsync().run((async) {
+ var fired = false;
+ var timer = new RestartableTimer(new Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.reset();
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+
+ async.elapse(new Duration(seconds: 1));
+ expect(fired, isTrue);
+ });
+ });
+
+ test("re-runs the callback if the timer is reset after firing", () {
+ new FakeAsync().run((async) {
+ var fired = 0;
+ var timer = new RestartableTimer(new Duration(seconds: 5), () {
+ fired++;
+ });
+
+ async.elapse(new Duration(seconds: 5));
+ expect(fired, equals(1));
+ timer.reset();
+
+ async.elapse(new Duration(seconds: 5));
+ expect(fired, equals(2));
+ timer.reset();
+
+ async.elapse(new Duration(seconds: 5));
+ expect(fired, equals(3));
+ });
+ });
+
+ test("runs the callback if the timer is reset after being canceled", () {
+ new FakeAsync().run((async) {
+ var fired = false;
+ var timer = new RestartableTimer(new Duration(seconds: 5), () {
+ fired = true;
+ });
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.cancel();
+
+ async.elapse(new Duration(seconds: 4));
+ expect(fired, isFalse);
+ timer.reset();
+
+ async.elapse(new Duration(seconds: 5));
+ expect(fired, isTrue);
+ });
+ });
+
+ test("only runs the callback once if the timer isn't reset", () {
+ new FakeAsync().run((async) {
+ var timer = new RestartableTimer(
+ new Duration(seconds: 5),
+ expectAsync(() {}, count: 1));
+ async.elapse(new Duration(seconds: 10));
+ });
+ });
+}