Line data Source code
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 : import 'dart:async';
6 :
7 : /// A collection of futures waits until all added [Future]s complete.
8 : ///
9 : /// Futures are added to the group with [add]. Once you're finished adding
10 : /// futures, signal that by calling [close]. Then, once all added futures have
11 : /// completed, [future] will complete with a list of values from the futures in
12 : /// the group, in the order they were added.
13 : ///
14 : /// If any added future completes with an error, [future] will emit that error
15 : /// and the group will be closed, regardless of the state of other futures in
16 : /// the group.
17 : ///
18 : /// This is similar to [Future.wait] with `eagerError` set to `true`, except
19 : /// that a [FutureGroup] can have futures added gradually over time rather than
20 : /// needing them all at once.
21 : class FutureGroup<T> implements Sink<Future<T>> {
22 : /// The number of futures that have yet to complete.
23 : var _pending = 0;
24 :
25 : /// Whether [close] has been called.
26 : var _closed = false;
27 :
28 : /// The future that fires once [close] has been called and all futures in the
29 : /// group have completed.
30 : ///
31 : /// This will also complete with an error if any of the futures in the group
32 : /// fails, regardless of whether [close] was called.
33 10 : Future<List<T>> get future => _completer.future;
34 : final _completer = new Completer<List<T>>();
35 :
36 : /// Whether this group has no pending futures.
37 0 : bool get isIdle => _pending == 0;
38 :
39 : /// A broadcast stream that emits a `null` event whenever the last pending
40 : /// future in this group completes.
41 : ///
42 : /// Once this group isn't waiting on any futures *and* [close] has been
43 : /// called, this stream will close.
44 : Stream get onIdle {
45 0 : if (_onIdleController == null) {
46 0 : _onIdleController = new StreamController.broadcast(sync: true);
47 : }
48 0 : return _onIdleController.stream;
49 : }
50 :
51 : StreamController _onIdleController;
52 :
53 : /// The values emitted by the futures that have been added to the group, in
54 : /// the order they were added.
55 : ///
56 : /// The slots for futures that haven't completed yet are `null`.
57 : final _values = new List<T>();
58 :
59 : /// Wait for [task] to complete.
60 : void add(Future<T> task) {
61 5 : if (_closed) throw new StateError("The FutureGroup is closed.");
62 :
63 : // Ensure that future values are put into [values] in the same order they're
64 : // added to the group by pre-allocating a slot for them and recording its
65 : // index.
66 10 : var index = _values.length;
67 10 : _values.add(null);
68 :
69 10 : _pending++;
70 5 : task.then((value) {
71 10 : if (_completer.isCompleted) return null;
72 :
73 10 : _pending--;
74 10 : _values[index] = value;
75 :
76 10 : if (_pending != 0) return null;
77 5 : if (_onIdleController != null) _onIdleController.add(null);
78 :
79 5 : if (!_closed) return null;
80 5 : if (_onIdleController != null) _onIdleController.close();
81 15 : _completer.complete(_values);
82 5 : }).catchError((error, stackTrace) {
83 0 : if (_completer.isCompleted) return null;
84 0 : _completer.completeError(error, stackTrace);
85 : });
86 : }
87 :
88 : /// Signals to the group that the caller is done adding futures, and so
89 : /// [future] should fire once all added futures have completed.
90 : void close() {
91 5 : _closed = true;
92 10 : if (_pending != 0) return;
93 10 : if (_completer.isCompleted) return;
94 15 : _completer.complete(_values);
95 : }
96 : }
|