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 |