Stream 顾名思义就是流,简单理解,其实就是一个异步数据队列而已。我们知道队列的特点是先进先出的,Stream也正是如此。
Stream 分为两种,单订阅流(single subscription) 和 广播流(broadcast)。
单订阅流
单订阅流的特点是只允许存在一个监听器,即使该监听器被取消后,也不允许再次注册监听器。
如何创建Stream
Stream.periodic
periodic可以创建一个 按一定间隔,重复发出事件的流。
import 'dart:async';
void main() {
// test();
testPeriodic();
}
testPeriodic() async {
// 使用 periodic 创建流,第一个参数为间隔时间,第二个参数为回调函数,这个回调函数参数是整型(0开始递增)
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
// await for循环从流中读取
await for (var i in stream) {
print(i);
}
}
int callback(int computationCount) {
// print("value $value");
return computationCount;
}
打印结果
0
1
2
3
4
…
每一秒生成一次自然数列,callback函数用于对生成的整数(computationCount,从0开始递增)进行处理,处理后再放入Stream中。这里并未处理,直接返回了。要注意,这个流是无限的。当然也可以约束条件使之停止。在后面会介绍如何给流设置条件。
约束条件
take 和 takeWhile
Stream take(int count) 用于 约束Stream中的元素数量,当流发出 count 个数据事件时,事件将会结束,stream is done。
testPeriodic() async {
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.take(5);// 当放入5个元素后,监听会停止,Stream会关闭
// await for循环从流中读取
await for (var i in stream) {
print(i);
}
}
打印结果:
0
1
2
3
4
Stream takeWhile(bool test(T element)) 与 take 类似,它是参数是一个返回值为bool的函数,函数功能是对当前元素进行判断,不满足条件则取消监听。
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.takeWhile((i) {
return i <= 5;
}
skip 和 skipWhile
表示从Stream中跳过前count个元素
testPeriodic() async {
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
stream = stream.takeWhile((i) {
return i <= 5;
});
stream = stream.skip(3);//表示从Stream中跳过前两个元素
// await for循环从流中读取
await for (var i in stream) {
print(i);
}
}
skipWhile(bool test(T element)) 方法与takeWhile用法是相同的,传入一个函数对结果进行判断,表示跳过满足条件的。
有几个注意事项
- 该方法只是从Stream中获取元素时跳过,被跳过的元素依然是被执行了的,所耗费的时间依然存在,其实只是跳过了执行完的结果而已。
- 如果该流发出的数据事件小于count,完成之前 stream将不会发射任何事件。
Stream.fromFuture
Stream.fromFuture(Future.delayed(Duration(seconds: 2), () {
return "Hello";
})).listen((onData) {
print(onData);
}, onError: (e) {
print("onError $e");
}, onDone: () {
print("onDone");
}, cancelOnError: false);//cancelOnError 为true时,出现onError时,onDone将不会回调
打印结果
Hello
onDone
该方法从一个Future创建Stream,当Future执行完成时,就会放入Stream中,而后从Stream中将任务完成的结果取出。
监听Stream的方法:
监听一个流最常见的方法就是listen。当有事件发出时,流将会通知listener。Listen方法提供了这几种触发事件:
- onData(必填):收到数据时触发
- onError:收到Error时触发
- onDone:结束时触发
- cancelOnError:遇到第一个Error时是否取消订阅,默认为false。cancelOnError 为true时,出现onError时,onDone将不会回调
顺便提一下:
获取Stream结果
Stream.fromFutures
可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等.
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(new Duration(seconds: 1), () {
return "hello";
}),
// 抛出一个异常
Future.delayed(new Duration(seconds: 2), () {
throw AssertionError("Error");
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return 520;
})
]).listen((data) {
print(data);
}, onError: (e) {
print(e.message);
}, onDone: () {
print("onDone");
});
打印结果
hello
Error
520
onDone
Stream.fromIterable
该方法从一个集合创建Stream
List<int> list = [1, 2, 3];
Stream.fromIterable(list).listen((data) {
print(data);
}, onError: (e) {
print(e.message);
}, onDone: () {
print("onDone");
},cancelOnError: false);
打印结果
1
2
3
onDone
Stream.value
Dart2.5 新增的方法,用于从单个值创建Stream。
Stream stream = Stream.value("Hello");
// await for循环从流中读取
stream.forEach((result){
print(result);
});
StreamController
StreamController 工具类 就如同一个管道,在这个管道中封装了一个 Stream ,并向我们提供了两个接口来操作 Stream 。分别是:
- sink 从Stream中的一端插入数据
- stream 从Stream的另一外弹出数据
//任意类型的流,也可定义泛型
StreamController controller = StreamController();
// 监听这个流的出口,当有data流出时,打印这个data
controller.stream.listen((data) => print(data),
onError: (e) => print(e),
onDone: () => print("onDone"),
cancelOnError: false);
controller.add(123);
controller.add("hello");
controller.sink.add('world');
controller.addError("this is error");
controller.add(false);
controller.close();// 调用close方法,结束Stream中的逻辑处理,释放资源,如果不调用close方法,将不会触发onDone回调。
打印结果
123
hello
world
this is error
false
onDone
监听Stream队列
细心的你可能看到,上述代码用过多种方式监听Stream,用过四种方式,await for循环,这也是官方推荐的方式,看起来更简洁友好,是使用forEach方法或listen方法。最后一种方式是 将Stream中所有数据存储在List中,然后遍历list,代码如下:
testList() async {
Stream stream = Stream.fromIterable([1, 2, 3]);
List list = await stream.toList();
for (var i in list) {
print(i);
}
}
广播流
在普通的单订阅流中调用两次listen会报错
Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (computationCount)=>computationCount);
stream.listen(print);
stream.listen(print);
将会报异常
Unhandled exception:
Bad state: Stream has already been listened to.
广播流则可以允许多个监听器存在,就如同广播一样,凡是监听了广播流,每个监听器都能获取到数据。
要注意,如果在触发事件时将监听者正添加到广播流,当前正在触发的事件和之前的时间,监听着都不会收到。如果取消监听,监听者会立即停止接收事件。
创建广播流方式
一种直接从Stream创建,另一种使用StreamController创建
从Stream创建
// 调用 Stream 的 asBroadcastStream 方法创建
Stream<int> stream = Stream<int>.fromIterable([1, 2, 3])
.asBroadcastStream();
stream.listen((i)=>print("listener1:$i"));
stream.listen((i)=>print("listener2:$i"));
打印结果
listener1:1
listener2:1
listener1:2
listener2:2
listener1:3
listener2:3
从StreamController创建
// 创建广播流
StreamController streamController = StreamController.broadcast();
Stream stream = streamController.stream;
//设置监听
stream.listen((i) => print("listener1:$i"));
stream.listen((i) => print("listener2:$i"));
//添加事件
streamController.add("event1");
streamController.add("event2");
打印结果
listener1:event1
listener2:event1
listener1:event2
listener2:event2
数据装换
StreamTransformer
stream通过 数据转换方法 transform,传入参数StreamTransformer(可以把StreamTransformer当做一个数据转换的策略),返回一个新的Stream。
通俗的讲:就是把一个 Stream 作为输入,然后经过计算或数据转换,输出为另一个 Stream。另一个 Stream 中的数据类型可以不同于原类型,数据多少也可以不同。
我们写一个例子,将队列里的小写字母转换为大写字母
StreamController<String> controller = StreamController<String>();
// 创建 StreamTransformer对象
StreamTransformer streamTransformer = StreamTransformer<String, String>.fromHandlers(
handleData: (String data, EventSink sink) {
// 操作数据后,转换为 double 类型
sink.add(data.toUpperCase());
},
handleError: (error, stacktrace, sink) {
sink.addError('wrong: $error');
},
handleDone: (sink) {
sink.close();
},
);
// 调用流的transform方法,传入转换对象
Stream<String> stream = controller.stream.transform(streamTransformer);
stream.listen(print,onDone: ()=>print("onDone"));
// 添加数据,这里的类型是int
controller.add('a');
controller.add('b');
controller.add('c');
// 调用后,触发handleDone回调
controller.close();
输出结果
A
B
C
onDone
总结 Stream 和 Future
Stream 和 Future 是 Dart 异步处理的核心 API。
Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。但是 Future 只能表示一次异步获得的数据。而 Stream 表示多次异步获得的数据。比如界面上的按钮可能会被用户点击多次,所以按钮上的点击事件(onClick)就是一个 Stream 。
简单地说,Future将返回一个值,而Stream将返回多次值。