Line data Source code
1 : // Copyright (c) 2016, 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 stream that combines the values of other streams.
8 : ///
9 : /// This emits lists of collected values from each input stream. The first list
10 : /// contains the first value emitted by each stream, the second contains the
11 : /// second value, and so on. The lists have the same ordering as the iterable
12 : /// passed to [new StreamZip].
13 : ///
14 : /// Any errors from any of the streams are forwarded directly to this stream.
15 : class StreamZip<T> extends Stream<List<T>> {
16 : final Iterable<Stream<T>> _streams;
17 :
18 0 : StreamZip(Iterable<Stream<T>> streams) : _streams = streams;
19 :
20 : StreamSubscription<List<T>> listen(void onData(List<T> data),
21 : {Function onError, void onDone(), bool cancelOnError}) {
22 : cancelOnError = identical(true, cancelOnError);
23 0 : var subscriptions = <StreamSubscription<T>>[];
24 : StreamController<List<T>> controller;
25 : List<T> current;
26 : int dataCount = 0;
27 :
28 : /// Called for each data from a subscription in [subscriptions].
29 : void handleData(int index, T data) {
30 0 : current[index] = data;
31 0 : dataCount++;
32 0 : if (dataCount == subscriptions.length) {
33 : var data = current;
34 0 : current = new List(subscriptions.length);
35 : dataCount = 0;
36 0 : for (int i = 0; i < subscriptions.length; i++) {
37 0 : if (i != index) subscriptions[i].resume();
38 : }
39 0 : controller.add(data);
40 : } else {
41 0 : subscriptions[index].pause();
42 : }
43 : }
44 :
45 : /// Called for each error from a subscription in [subscriptions].
46 : /// Except if [cancelOnError] is true, in which case the function below
47 : /// is used instead.
48 : void handleError(Object error, StackTrace stackTrace) {
49 0 : controller.addError(error, stackTrace);
50 : }
51 :
52 : /// Called when a subscription has an error and [cancelOnError] is true.
53 : ///
54 : /// Prematurely cancels all subscriptions since we know that we won't
55 : /// be needing any more values.
56 : void handleErrorCancel(Object error, StackTrace stackTrace) {
57 0 : for (int i = 0; i < subscriptions.length; i++) {
58 0 : subscriptions[i].cancel();
59 : }
60 0 : controller.addError(error, stackTrace);
61 : }
62 :
63 : void handleDone() {
64 0 : for (int i = 0; i < subscriptions.length; i++) {
65 0 : subscriptions[i].cancel();
66 : }
67 0 : controller.close();
68 : }
69 :
70 : try {
71 0 : for (var stream in _streams) {
72 0 : int index = subscriptions.length;
73 0 : subscriptions.add(stream.listen((data) {
74 0 : handleData(index, data);
75 : },
76 : onError: cancelOnError ? handleError : handleErrorCancel,
77 : onDone: handleDone,
78 : cancelOnError: cancelOnError));
79 : }
80 : } catch (e) {
81 0 : for (int i = subscriptions.length - 1; i >= 0; i--) {
82 0 : subscriptions[i].cancel();
83 : }
84 : rethrow;
85 : }
86 :
87 0 : current = new List(subscriptions.length);
88 :
89 0 : controller = new StreamController<List<T>>(onPause: () {
90 0 : for (int i = 0; i < subscriptions.length; i++) {
91 : // This may pause some subscriptions more than once.
92 : // These will not be resumed by onResume below, but must wait for the
93 : // next round.
94 0 : subscriptions[i].pause();
95 : }
96 : }, onResume: () {
97 0 : for (int i = 0; i < subscriptions.length; i++) {
98 0 : subscriptions[i].resume();
99 : }
100 : }, onCancel: () {
101 0 : for (int i = 0; i < subscriptions.length; i++) {
102 : // Canceling more than once is safe.
103 0 : subscriptions[i].cancel();
104 : }
105 : });
106 :
107 0 : if (subscriptions.isEmpty) {
108 0 : controller.close();
109 : }
110 0 : return controller.stream.listen(onData,
111 : onError: onError, onDone: onDone, cancelOnError: cancelOnError);
112 : }
113 : }
|