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 0 : @override 21 : StreamSubscription<List<T>> listen(void Function(List<T>)? onData, 22 : {Function? onError, void Function()? onDone, bool? cancelOnError}) { 23 : cancelOnError = identical(true, cancelOnError); 24 0 : var subscriptions = <StreamSubscription<T>>[]; 25 : late StreamController<List<T>> controller; 26 : late List<T?> current; 27 : var dataCount = 0; 28 : 29 : /// Called for each data from a subscription in [subscriptions]. 30 0 : void handleData(int index, T data) { 31 0 : current[index] = data; 32 0 : dataCount++; 33 0 : if (dataCount == subscriptions.length) { 34 0 : var data = List<T>.from(current); 35 0 : current = List<T?>.filled(subscriptions.length, null); 36 : dataCount = 0; 37 0 : for (var i = 0; i < subscriptions.length; i++) { 38 0 : if (i != index) subscriptions[i].resume(); 39 : } 40 0 : controller.add(data); 41 : } else { 42 0 : subscriptions[index].pause(); 43 : } 44 : } 45 : 46 : /// Called for each error from a subscription in [subscriptions]. 47 : /// Except if [cancelOnError] is true, in which case the function below 48 : /// is used instead. 49 0 : void handleError(Object error, StackTrace stackTrace) { 50 0 : controller.addError(error, stackTrace); 51 : } 52 : 53 : /// Called when a subscription has an error and [cancelOnError] is true. 54 : /// 55 : /// Prematurely cancels all subscriptions since we know that we won't 56 : /// be needing any more values. 57 0 : void handleErrorCancel(Object error, StackTrace stackTrace) { 58 0 : for (var i = 0; i < subscriptions.length; i++) { 59 0 : subscriptions[i].cancel(); 60 : } 61 0 : controller.addError(error, stackTrace); 62 : } 63 : 64 0 : void handleDone() { 65 0 : for (var i = 0; i < subscriptions.length; i++) { 66 0 : subscriptions[i].cancel(); 67 : } 68 0 : controller.close(); 69 : } 70 : 71 : try { 72 0 : for (var stream in _streams) { 73 0 : var index = subscriptions.length; 74 0 : subscriptions.add(stream.listen((data) { 75 : handleData(index, data); 76 : }, 77 : onError: cancelOnError ? handleError : handleErrorCancel, 78 : onDone: handleDone, 79 : cancelOnError: cancelOnError)); 80 : } 81 : } catch (e) { 82 0 : for (var i = subscriptions.length - 1; i >= 0; i--) { 83 0 : subscriptions[i].cancel(); 84 : } 85 : rethrow; 86 : } 87 : 88 0 : current = List<T?>.filled(subscriptions.length, null); 89 : 90 0 : controller = StreamController<List<T>>(onPause: () { 91 0 : for (var i = 0; i < subscriptions.length; i++) { 92 : // This may pause some subscriptions more than once. 93 : // These will not be resumed by onResume below, but must wait for the 94 : // next round. 95 0 : subscriptions[i].pause(); 96 : } 97 0 : }, onResume: () { 98 0 : for (var i = 0; i < subscriptions.length; i++) { 99 0 : subscriptions[i].resume(); 100 : } 101 0 : }, onCancel: () { 102 0 : for (var i = 0; i < subscriptions.length; i++) { 103 : // Canceling more than once is safe. 104 0 : subscriptions[i].cancel(); 105 : } 106 : }); 107 : 108 0 : if (subscriptions.isEmpty) { 109 0 : controller.close(); 110 : } 111 0 : return controller.stream.listen(onData, 112 : onError: onError, onDone: onDone, cancelOnError: cancelOnError); 113 : } 114 : }