Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 // TODO(nweiz): Use this from the async package when | |
| 6 // https://codereview.chromium.org/1266603005/ lands. | |
| 7 library test.util.cancelable_future; | |
| 8 | |
| 9 import 'dart:async'; | |
| 10 | |
| 11 import 'package:async/async.dart'; | |
| 12 | |
| 13 /// A [Future] that can be cancelled. | |
| 14 /// | |
| 15 /// When this is cancelled, that means it won't complete either successfully or | |
| 16 /// with an error. | |
| 17 /// | |
| 18 /// In general it's a good idea to only have a single non-branching chain of | |
| 19 /// cancellable futures. If there are multiple branches, any of them that aren't | |
| 20 /// closed explicitly will throw a [CancelException] once one of them is | |
| 21 /// cancelled. | |
| 22 class CancelableFuture<T> implements Future<T> { | |
| 23 /// The completer that produced this future. | |
| 24 /// | |
| 25 /// This is canceled when [cancel] is called. | |
| 26 final CancelableCompleter<T> _completer; | |
| 27 | |
| 28 /// The future wrapped by [this]. | |
| 29 Future<T> _inner; | |
| 30 | |
| 31 /// Whether this future has been canceled. | |
| 32 /// | |
| 33 /// This is tracked individually for each future because a canceled future | |
| 34 /// shouldn't emit events, but the completer will throw a [CancelException]. | |
| 35 bool _canceled = false; | |
| 36 | |
| 37 CancelableFuture._(this._completer, Future<T> inner) { | |
| 38 // Once this future is canceled, it should never complete. | |
| 39 _inner = inner.whenComplete(() { | |
| 40 if (_canceled) return new Completer().future; | |
| 41 }); | |
| 42 } | |
| 43 | |
| 44 /// Creates a [CancelableFuture] wrapping [inner]. | |
| 45 /// | |
| 46 /// When this future is canceled, [onCancel] will be called. The callback may | |
| 47 /// return a Future to indicate that asynchronous work has to be done to | |
| 48 /// cancel the future; this Future will be returned by [cancel]. | |
| 49 factory CancelableFuture.fromFuture(Future<T> inner, [onCancel()]) { | |
| 50 var completer = new CancelableCompleter<T>(onCancel); | |
| 51 completer.complete(inner); | |
| 52 return completer.future; | |
| 53 } | |
| 54 | |
| 55 /// Creates a [Stream] containing the result of this future. | |
| 56 /// | |
| 57 /// If this Future is canceled, the Stream will not produce any events. If a | |
| 58 /// subscription to the stream is canceled, this is as well. | |
| 59 Stream<T> asStream() { | |
| 60 var controller = new StreamController<T>( | |
| 61 sync: true, onCancel: _completer._cancel); | |
| 62 | |
| 63 _inner.then((value) { | |
| 64 controller.add(value); | |
| 65 controller.close(); | |
| 66 }, onError: (error, stackTrace) { | |
| 67 controller.addError(error, stackTrace); | |
| 68 controller.close(); | |
| 69 }); | |
| 70 return controller.stream; | |
| 71 } | |
| 72 | |
| 73 /// Returns [this] as a normal future. | |
| 74 /// | |
| 75 /// The returned future is different from this one in the following ways: | |
| 76 /// | |
| 77 /// * Its methods don't return [CancelableFuture]s. | |
| 78 /// | |
| 79 /// * It doesn't support [cancel] or [asFuture]. | |
| 80 /// | |
| 81 /// * The [Stream] returned by [asStream] won't cancel the future if it's | |
| 82 /// canceled. | |
| 83 /// | |
| 84 /// * If a [timeout] times out, it won't cancel the future. | |
| 85 Future asFuture() => _inner; | |
| 86 | |
| 87 CancelableFuture catchError(Function onError, {bool test(error)}) => | |
|
kevmoo
2015/07/29 23:03:46
docs for these?
nweiz
2015/07/29 23:18:10
Generally you shouldn't document inherited members
| |
| 88 new CancelableFuture._( | |
| 89 _completer, _inner.catchError(onError, test: test)); | |
| 90 | |
| 91 CancelableFuture then(onValue(T value), {Function onError}) => | |
| 92 new CancelableFuture._( | |
| 93 _completer, _inner.then(onValue, onError: onError)); | |
| 94 | |
| 95 CancelableFuture<T> whenComplete(action()) => | |
| 96 new CancelableFuture<T>._(_completer, _inner.whenComplete(action)); | |
| 97 | |
| 98 /// Time-out the future computation after [timeLimit] has passed. | |
| 99 /// | |
| 100 /// When the future times out, it will be canceled. Note that the return value | |
| 101 /// of the completer's `onCancel` callback will be ignored by default, and any | |
| 102 /// errors it produces silently dropped. To avoid this, call [cancel] | |
| 103 /// explicitly in [onTimeout]. | |
| 104 CancelableFuture timeout(Duration timeLimit, {onTimeout()}) { | |
| 105 var wrappedOnTimeout = () { | |
| 106 // Ignore errors here because there's no good way to pipe them to the | |
| 107 // caller without screwing up [onTimeout]. | |
| 108 _completer._cancel().catchError((_) {}); | |
| 109 if (onTimeout != null) return onTimeout(); | |
| 110 throw new TimeoutException("Future not completed", timeLimit); | |
| 111 }; | |
| 112 | |
| 113 return new CancelableFuture._( | |
| 114 _completer, _inner.timeout(timeLimit, onTimeout: wrappedOnTimeout)); | |
| 115 } | |
| 116 | |
| 117 /// Cancels this future. | |
| 118 /// | |
| 119 /// This returns the [Future] returned by the [CancelableCompleter]'s | |
| 120 /// `onCancel` callback. Unlike [Stream.cancel], it never returns `null`. | |
| 121 Future cancel() { | |
| 122 _canceled = true; | |
| 123 return _completer._cancel(); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 /// A completer for a [CancelableFuture]. | |
| 128 class CancelableCompleter<T> implements Completer<T> { | |
| 129 /// The completer for the wrapped future. | |
| 130 final Completer<T> _inner; | |
| 131 | |
| 132 /// The callback to call if the future is canceled. | |
| 133 final ZoneCallback _onCancel; | |
| 134 | |
| 135 CancelableFuture<T> get future => _future; | |
| 136 CancelableFuture<T> _future; | |
| 137 | |
| 138 bool get isCompleted => _isCompleted; | |
| 139 bool _isCompleted = false; | |
| 140 | |
| 141 /// Whether the completer was canceled before being completed. | |
| 142 bool get isCanceled => _isCanceled; | |
| 143 bool _isCanceled = false; | |
| 144 | |
| 145 /// Whether the completer has fired. | |
| 146 /// | |
| 147 /// This is distinct from [isCompleted] when a [Future] is passed to | |
| 148 /// [complete]; this won't be `true` until that [Future] fires. | |
| 149 bool _fired = false; | |
| 150 | |
| 151 /// Creates a new completer for a [CancelableFuture]. | |
| 152 /// | |
| 153 /// When the future is canceled, as long as the completer hasn't yet | |
| 154 /// completed, [onCancel] is called. The callback may return a [Future]; if | |
| 155 /// so, that [Future] is returned by [CancelableFuture.cancel]. | |
| 156 CancelableCompleter([this._onCancel]) | |
| 157 : _inner = new Completer<T>() { | |
| 158 _future = new CancelableFuture<T>._(this, _inner.future); | |
| 159 } | |
| 160 | |
| 161 void complete([value]) { | |
| 162 if (_isCompleted) throw new StateError("Future already completed"); | |
| 163 _isCompleted = true; | |
| 164 | |
| 165 if (_isCanceled) return; | |
| 166 if (value is! Future) { | |
| 167 _fired = true; | |
| 168 _inner.complete(value); | |
| 169 return; | |
| 170 } | |
| 171 | |
| 172 value.then((result) { | |
| 173 if (_isCanceled) return; | |
| 174 _fired = true; | |
| 175 _inner.complete(result); | |
| 176 }, onError: (error, stackTrace) { | |
| 177 if (_isCanceled) return; | |
| 178 _fired = true; | |
| 179 _inner.completeError(error, stackTrace); | |
| 180 }); | |
| 181 } | |
| 182 | |
| 183 void completeError(Object error, [StackTrace stackTrace]) { | |
| 184 if (_isCompleted) throw new StateError("Future already completed"); | |
| 185 _isCompleted = true; | |
| 186 | |
| 187 if (_isCanceled) return; | |
| 188 _fired = true; | |
| 189 _inner.completeError(error, stackTrace); | |
| 190 } | |
| 191 | |
| 192 /// Cancel the completer. | |
| 193 Future _cancel() => _cancelMemo.runOnce(() { | |
| 194 if (_fired) return null; | |
| 195 _isCanceled = true; | |
| 196 | |
| 197 // Throw an catch to get access to the current stack trace. | |
| 198 try { | |
| 199 throw new CancelException(); | |
| 200 } catch (error, stackTrace) { | |
| 201 _inner.completeError(error, stackTrace); | |
| 202 } | |
| 203 | |
| 204 if (_onCancel != null) return _onCancel(); | |
| 205 }); | |
| 206 final _cancelMemo = new AsyncMemoizer(); | |
| 207 } | |
| 208 | |
| 209 /// An exception thrown when a [CancelableFuture] is canceled. | |
| 210 /// | |
| 211 /// Since a canceled [CancelableFuture] doesn't receive any more events, this | |
| 212 /// will only be passed to other branches of the future chain. | |
| 213 class CancelException implements Exception { | |
| 214 CancelException(); | |
| 215 | |
| 216 String toString() => "This Future has been canceled."; | |
| 217 } | |
| OLD | NEW |