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 0 : Future<List<T>> get future => _completer.future; 34 : final _completer = 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 0 : Stream get onIdle => 45 0 : (_onIdleController ??= StreamController.broadcast(sync: true)).stream; 46 : 47 : StreamController? _onIdleController; 48 : 49 : /// The values emitted by the futures that have been added to the group, in 50 : /// the order they were added. 51 : /// 52 : /// The slots for futures that haven't completed yet are `null`. 53 : final _values = <T?>[]; 54 : 55 : /// Wait for [task] to complete. 56 0 : @override 57 : void add(Future<T> task) { 58 0 : if (_closed) throw StateError('The FutureGroup is closed.'); 59 : 60 : // Ensure that future values are put into [values] in the same order they're 61 : // added to the group by pre-allocating a slot for them and recording its 62 : // index. 63 0 : var index = _values.length; 64 0 : _values.add(null); 65 : 66 0 : _pending++; 67 0 : task.then((value) { 68 0 : if (_completer.isCompleted) return null; 69 : 70 0 : _pending--; 71 0 : _values[index] = value; 72 : 73 0 : if (_pending != 0) return null; 74 0 : var onIdleController = _onIdleController; 75 0 : if (onIdleController != null) onIdleController.add(null); 76 : 77 0 : if (!_closed) return null; 78 0 : if (onIdleController != null) onIdleController.close(); 79 0 : _completer.complete(_values.whereType<T>().toList()); 80 0 : }).catchError((Object error, StackTrace stackTrace) { 81 0 : if (_completer.isCompleted) return null; 82 0 : _completer.completeError(error, stackTrace); 83 : }); 84 : } 85 : 86 : /// Signals to the group that the caller is done adding futures, and so 87 : /// [future] should fire once all added futures have completed. 88 0 : @override 89 : void close() { 90 0 : _closed = true; 91 0 : if (_pending != 0) return; 92 0 : if (_completer.isCompleted) return; 93 0 : _completer.complete(_values.whereType<T>().toList()); 94 : } 95 : }