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)}) => |
| 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 |