一、Flutter的Future
我们都知道flutter的开发语言是Dart,而Dart是一个在单线程中运行的程序,这意味着:如果程序在执行中遇到一个需要长时间的执行的操作,程序将会被冻结。为了避免造成程序的冻结,我们一般使用Future异步来处理耗时操作。
1.什么是Future
Future<T>表示一个指定类型的异步操作结果,当一个返回 future 对象的函数被调用时,主要是分两部分执行。
.运行状态:会将这个函数放入队列等待执行并返回一个未完成的Future对象
.完成状态:当函数操作执行完成,Future对象就会携带一个值变成完成状态
我们可以来看一个案例:
static future1() {
Future future = Future(() {
MyUtil.toPrint('我是第一个future'); //1
});
future.then((value) => MyUtil.toPrint('我是future的then')); //2
MyUtil.toPrint('future1方法体'); //3
//print:312
}
通过案例我们可以看出,程序是首先执行完future1方法函数,再去执行任务栈中的内容,也就是说把future放在任务栈中,then里面的方法会在future处于完成状态时,立马执行。
2.await和then的区别
一般是有两种方式可以处理返回的结果,一种是await和async,一种是.then
共同点是:都是等待future执行完毕之后再去处理
区别:.then的方法是只等待当前future执行完后再去执行,而await是等待之前所有非异步的代码执行完成,并不单单只是当前的future
具体案例如下:
static future2() async{
Future future = Future(() {
MyUtil.toPrint('我是第一个future'); //1
});
await future;
MyUtil.toPrint('等待future执行完毕');//2
MyUtil.toPrint('future1方法体'); //3
//print:123
}
3.future的执行机制
之前有提到过,当任务栈中任务被执行完后,立刻运行then中的函数,所以说,then中的回调函数执行的顺序是并不取决于注册的顺序,而是跟Future被加入到任务栈的顺序有关。
static future3() {
Future future1 = Future(() {
MyUtil.toPrint('我是future1'); //1
});
Future future2 = Future(() {
MyUtil.toPrint('我是future2'); //2
});
Future future3 = Future(() {
MyUtil.toPrint('我是future3'); //3
});
future3.then((value) => MyUtil.toPrint('我是future3的then')); //4
future2.then((value) => MyUtil.toPrint('我是future2的then')); //5
future1.then((value) => MyUtil.toPrint('我是future1的then')); //6
//print:162534
}
接着我们试着在future里面加个延时,再看下执行的顺序就会发现,flutter的then只在当前的future执行完后就会立马执行,如果当前的future里面嵌套了一个新的future,是不会等待新的future执行完毕的,如:
static future4() {
Future future1 = Future(() {
Future.delayed(const Duration(seconds: 3)).then((value) {
MyUtil.toPrint('我是future1');
}); //1;
});
Future future2 = Future(() {
Future.delayed(const Duration(seconds: 2)).then((value) {
MyUtil.toPrint('我是future2');
}); //2;
});
Future future3 = Future(() {
Future.delayed(const Duration(seconds: 1)).then((value) {
MyUtil.toPrint('我是future3');
}); //3;
});
future3.then((value) => MyUtil.toPrint('我是future3的then')); //4
future2.then((value) => MyUtil.toPrint('我是future2的then')); //5
future1.then((value) => MyUtil.toPrint('我是future1的then')); //6
//print:654321
}
4.项目中future的实用场景
项目中我们用到最多的就是通过future去处理网络请求,比如非常常用的一个场景就是,网络请求前我们需要弹一个加载框,等接口请求完成后,关闭弹框,这种情况通过await+async或者是.then都能实现。但是今天要说的是另外一种场景,就是进页面之前我们需要调多个接口,等待所有接口调用完成之后,我们再关闭加载框,这个时候我就需要用到future里面的await.
具体案例如下:
static future5() {
Future.wait([test1(), test2()]).then((value) {
toPrint(value[0]);//3
toPrint(value[1]);//4
});
}
static Future<String> test1() async {
return Future.delayed(const Duration(milliseconds: 500)).then((value) {
toPrint('test1执行完毕');//1
return '我是test1字符串';
});
}
static Future<String> test2() async {
return Future.delayed(const Duration(milliseconds: 500)).then((value) {
toPrint('test2执行完毕');//2
return '我是test2字符串';
});
}
从上面的案例我们可以看出来,await后面的then里面执行的结果是在test1和test2两个方法都执行完成之后再输出的,我们可以思考一个问题,那么test1和test2的顺序是怎么执行的,我们可以将test1的延迟时间改成1500,打印输出会发现执行顺序变成了2134,所以await里面的异步方法并不是排队执行的,而是并行执行的。
一、Flutter的Stream
Stream和Future最大的共同点就是,Stream也是用来处理异步操作的。
1.什么是Stream
从字面意思上看,Stream就是流,主要是把事件放在流上面去处理,而Stream所用的设计模式则是观察者模式,观察者模式用过getx框架的可能会比较熟悉。getx的obx就是使用的观察者模式
2.Stream的构造方法
Stream可以通过两种形式去创建,一种是通过构造方法,一种是通过StreamController,而Stream的构造方法分为3种
- Stream.fromFuture,通过传递一个异步任务来创建Stream
static stream1() {
Stream stream = Stream.fromFuture(
Future.delayed(const Duration(milliseconds: 500)).then((value) {
return '我是Stream的future执行结果';
}));
stream.listen((event) {
toPrint(event);
});
}
- Stream.fromFutures,通过传递多个异步任务来创建Stream
static stream2() {
Stream stream = Stream.fromFutures([
getFuture(1, 500, '我是第1个Future'),
getFuture(2, 500, '我是第2个Future'),
getFuture(3, 500, '我是第3个Future')
]);
stream.listen((event) {
toPrint('event:$event');
},onDone: (){
toPrint('执行完成');
});
}
static getFuture(int type, int ms, String resultStr) {
return Future.delayed(Duration(milliseconds: ms)).then((value) {
toPrint('第$type个future执行完毕');
return resultStr;
});
}
执行结果:
第1个future执行完毕
event:我是第1个Future
第2个future执行完毕
event:我是第2个Future
第3个future执行完毕
event:我是第3个Future
执行完成
我们可以通过案例看到,这里的执行机制是:在执行多个异步任务的时候,每个异步任务执行完成后,都会走stream的监听回调,跟Future.await不同的是,Future.await是等所有异步任务执行完毕才走的回调,但是stream也可以在onDone的方法里面去监听到所有的异步任务完成的操作
- Stream.fromIterable 通过传递一个集合来创建Stream,集合中的每一个数据都会有自己的回调
static stream3(){
Stream stream = Stream.fromIterable([1,2,3,4]);
stream.listen((event) {
toPrint('event:$event');
});
//print:1234
}
当然这个集合可以是任何类型,有人可能会问,我可以在集合里面传异步方法吗,是可以传的,只是stream的监听就不会等待你的异步方法执行完才做回调了,所以在集合里面放异步方法就没有太多意义了
2.Stream的订阅流
其实Stream的类型是分为两种,一种是单订阅流,还有一种是多订阅流,首先我们来看单订阅流
跟上面介绍的Stream的创建方法不同的是,Stream的订阅在用完了是必须手动销毁的,主要分4步
- 创建StreamController
- 使用StreamSink作为事件入口
- 通过Stream用来监听数据,并返回StreamSubscription
- 通过StreamSubscription取消订阅,关闭流
//创建StreamController
StreamController streamCtrl = StreamController();
//用做添加事件的入口
StreamSink get sink => streamCtrl.sink;
//Stream用来监听数据
Stream get stream => streamCtrl.stream;
//Stream的订阅对象
StreamSubscription? subscription;
@override
void initState() {
//监听数据
subscription = stream.listen((event) {
setState(() {
showText += '$event ';
});
});
super.initState();
}
//添加数据
sendData() {
sink.add(random.nextInt(100));
}
@override
void dispose() {
//取消订阅,关闭流
subscription?.cancel();
streamCtrl.close();
super.dispose();
}
其实多订阅流的使用跟单订阅流的使用没有太大的区别,顾名思义,单订阅流是只能订阅一次,多订阅流可以订阅多次。并且单订阅流中,订阅者是可以接收到订阅之前的事件,而多订阅流 后面的订阅者不会接收到之前的事件。多订阅流的定义如下:
StreamController streamCtrl = StreamController.broadcast();
我们来看一下代码:
sink.add(999);
subscription = stream.listen((event) {
setState(() {
showText += '$event ';
});
});
如果是单订阅模式,sink.add(999);这行代码是生效的,如果是多订阅模式这行代码是不生效的
此外单订阅流也是可以转换为多订阅流的,如:
Stream broadCastStream = stream.asBroadcastStream();
3.StreamBuilder
StreamBuilder是Flutter中的一个Widget,它可以跟Steam结合起来使用,这个就有点像flutter的状态管理Provider,在builder里面处理接收到的信息,然后渲染到子控件中,在使用StreamBuilder组件时需要注意的是,如果使用的单订阅模式,StreamBuilder传入stream就相当于是已经订阅一次了,所以我们在初始化的时候再订阅一次stream的监听,是会报错的
Widget streamWidget() {
return StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Text(
'${snapshot.data}',
style: const TextStyle(color: Colors.red, fontSize: 12),
softWrap: true,
);
});
}
三、总结
Stream和Future都是用于接收异步事件数据,但是Future是表示单个计算结果的异步封装,而Stream表示的是多个序列化事件的异步封装
Stream可以接收多个异步操作的结果。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。所以Stream常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写