Flutter的Future和Stream

一、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常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容