Dart之Future和异步,说人话系列,Future,async、await

2,241 阅读10分钟

一、异步的三个朋友,Futureasyncawait

一.1 记住他们

  • Dart是单线程 模型语言,也就没有了所谓的主线程/子线程.一个线程完成所有异步。
  • 虽然是单线程,但是也需要做异步操作的事情,这里面涉及到了 Event QueueEvent Loop,但是实际上,也就离不开 Future类和asyncawait,干异步的时候这3个绕不开

通过图片,我们可以看到。有两个东西

  • Micortask Queue 微任务队列
  • Event Queue 事件队列
  • 队列队列,先进先出 先进先出 先进先出

Micortask Queue的优先级高,他们处理完才能处理Event Queue

不过呢,没有关系,平时基本不涉及到Micortask Queue,我们的网络操作,IO等异步才做都是在Event Queue里面操作的。

(异步应该都知道吧,专心拉屎是同步,一边拉屎一边玩手机是异步)

网上找的两个图。贴上

看个图1 image.png

看个图2

image.png

关于Future类和asyncawait的一些官方解释,喜欢跳转看下

一.2、主播大喊,三二一,上例子

例子一 最简单的演示

代码

import 'dart:core';
void main() {
  Future((){
    print('Future1  这里会被放在 EventQueu 中执行');
  });
  // 3秒后执行
  Future.delayed(Duration(seconds: 3), () {
    print('Future2 这里会被放在 EventQueu 中执行');
  });
}

.
.

输出

Future1  这里会被放在 EventQueu 中执行
Future2 这里会被放在 EventQueu 中执行

例子一 最简单的异步

  • 后写的先执行
import 'dart:core';

void main() {
  // 这里会后执行
  Future((){
    print('我是一个Future');
  });
  
  // 这里会执行
  print('我是写在Future的代码,但是我会显示执行,因为Future是仍到 事件队列 里的异步操作');
}

.
.
输出

我是写在Future的代码,但是我会显示执行,因为Future是仍到 事件队列 里的异步操作
我是一个Future

看到这里,有必要说下:

通常情况下:main > microtask queue > event queue

记住他 记住他 记住他 记住他!!!!!!!!!!!!!!!!!!!!!!!!!!!

所以啊。这个先后执行关系不用多说了吧,谁让Future是异步的呢

那么可以不可以,上面的代码按顺序来呢?也就是Future先执行呢?当然是可以的,需要用的 await

一.3、Future的3个状态

import 'dart:core';
void main() {
  Future(() {
    print('未完成状态');
  })
      .then((value) => print('已完成状态'))
      .catchError((value) => print('异常状态'));
}

或者写成 (阅读性差一些)

import 'dart:core';

void main() {
  Future(() {
    print('未完成状态');
  }).then((value) => print('已完成状态')).catchError((value) => print('异常状态'));
}

.
.
输出

未完成状态
已完成状态

二、awaitasync 登场

awaitasync 一般是搭配使用的,

前面的听过一个 最简单的异步 例子,我们可以看到的 main > microtask queue > event queue ,也就是main的代码指定好了 event queue里面的Future才被执行。

那么,怎么让代码按顺序来,也就是Future执行完成之后,再去执行他下方的代码呢?

答案就是使用 await.

二.1 await的作用 和 前提

await的作用

  • 表示等待该异步任务完成后才会继续往下执行。
  • await只能出现在异步函数内部,能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。
  • await只能在async函数出现

await 前提

await关键字使用必须满足两个条件
  • 当前函数必须是异步函数(即在函数头中包含关键字async的函数)
  • await修饰的任务必须是异步任务

二.2 async的作用

  • async用来表示函数是异步的,定义的函数会返回一个 Future 对象,可以使用 then 添加回调函数。
  • async函数,返回值是一个Future对象,如果没有返回Future对象,会自动将返回值包装成Future对象。
  • 使用async和await来使用Future,使代码看起来像是同步的代码,但实际上它们还是异步执行的

.
.

上代码:

Future,await,async 结合使用

import 'dart:core';

// 注意使用了 async 返回值一定是个Future对象 ,所以这里是main方法返回值是 Future<void> 
Future<void> main() async {
  print('第一行代码');
  /*
  --- await的作用:
  表示等待该异步任务完成后才会继续往下执行。
  await只能出现在异步函数内部,能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。

  --- await关键字使用必须满足两个条件:
  当前函数必须是异步函数(即在函数头中包含关键字async的函数);
  await修饰的任务必须是异步任务
  */
  var fut = await Future((){
    print('我是一个Future');
  });
  // 这里会执行
  print('我是写在Future之后的代码,因为前面使用了await,所以我变成后面执行了');
}

.
.

输出:

第一行代码
我是一个Future
我是写在Future之后的代码,因为前面使用了await,所以我变成后面执行了

到了这里,Future,await,async三兄弟就全部出场了,三者搭配是使用最非常多的场景。其实好像也不能说他们是三兄弟,但是就这么说着吧。

看到基础,基本的使用也就够了。

有兴趣可以接着看。

三、Future常见方法 value delayed

三、1 Future.value()

  • 创建一个返回指定value值的Future

(不说人话)通过value值创建一个Future对象,vuale可以是一个确定的值或者是一个future对象。该方法使用了内部的immediate函数,这个函数会使用scheduleMicrotaskmicrotask队列中更新Future的值。

value的例子

import 'dart:core';

void main() {
  var future=Future.value(1);
  print(future);
  //输出 Instance of 'Future<int>'

  var futS=Future.value('张三');
  print(futS);
  // 输出:Instance of 'Future<String>'
}

三.2、Future.delayed()

  • 创建一个延迟执行的future。
import 'dart:core';

void main() {
  var futureDelayed = Future.delayed(Duration(seconds: 2), () {
    print("Future.delayed");
    return 2;
  });
}

嗯,这个没什么好说的

四、处理Future结果。then、catchError、whenComplete、timeout

以下这4个方法常常用来处理Future的结果,其中then最为常用

  • then:创建完成Future对象后,可以通过then方法接收Future的结果。
  • catchError:当Future没有正确的获取到结果发生异常时,除了在then方法中注册onError回调外,还可通过catchError方法监听异常。
  • whenComplete:当该Future处于完成状态时,通过该方法注册的回调会被调用,无论结果是成功还是失败,相当与finally代码块。
  • timeout:超时处理

四.1、 then 方法

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});

  • 用于在Future完成的时候添加回调。
  • then方法的onValue回调函数的返回值为FutureOr<R>,代表该回调函数可以返回一个Future对象,或者返回一个T类型的值
  • then方法接收两个回调函数参数,onValue接收成功回调,可选的onError接收失败回调。

then 例子1

import 'dart:core';

void main() {
  var future = Future(() {
    //do compution
    return "future value";
  });
  future.then((value) {
    print(" 成功 onValue: $value");
  }, onError: (error) {
    print(" 失败 onError $error");
  });
}

.
.
输出:

 成功 onValue: future value

then 例子2

链式调用

import 'dart:core';

void main() {
  Future.value(1).then((value) {
    return Future.value(value + 2);
  }).then((value) {
    return Future.value(value + 3);
  }).then(print);
  //打印结果为6
}

then 例子3

通过then, 可以发起多个异步网络请求,从上到下顺序执行,而不是通过callback进行横向嵌套。

var future = Future(() {
   //do compution
    return "future value";
  });

future.then((value) {
    print("onValue: $value");
    var otherFuture = Future(() {
        //do request
        return "request value";
    });
    return otherFuture;
}, onError: (error) {
    print("onError $error");
}).then((value) {
    print("onValue: $value");
});


四.2、 catchError 方法

Future<T> catchError(Function onError, {bool test(Object error)});

Future没有正确的获取到结果发生异常时,除了在then方法中注册onError回调外,还可通过catchError方法监听异常。

例子1 catchError

import 'dart:core';

void main() {
  var future = Future(() {
    //do compution
    throw Exception("exception occured");
  });
  future.then((value){
    print("onValue: $value");
  }).catchError((error) {
    print("catchError $error");
  },);
  // 输出结果:catchError Exception: exception occured
}

catchError方法还有一个可以选的test函数参数。当发生异常时,会首先调用test函数,如果该函数返回false,异常将不会被catchError函数处理,会继续传递下去;如果test函数返回ture,catchError函数会处理该异常。如果未提供test函数,默认处理为true。

例子2 catchError 和 then 里面的onError

通过then方法的onError参数和catchError方法处理异常有什么不同呢?

这两种方式主要的区别是,通过catchError方法可以捕获到前一个then方法onValue函数中的异常,而通过onError函数方法,无法捕获同一个then方法onValue函数中的异常。

void main() {
  var future = Future(() {
    return "future value";
  });
  future.then((value){
    print("onValue: $value");
    throw Exception("exception occured");
  }, onError: (error) {
    print("onError $error");
  }).catchError((error) {
    print("catchError $error");
  });

}

.
.
输出

onValue: future value\
catchError Exception: exception occured\

异常被catchError捕获,而onError没有被调用。

四.3、 whenComplete

  • Future对象还有一个whenComplete方法,当该Future处于完成状态时,通过该方法注册的回调会被调用,无论结果是成功还是失败,相当与finally代码块。

  • whenComplete方法的参数类型也是FutureOr类型,返回值为Future<T>类型。表示在该方法中可以返回一个值或者是Future对象。

  • whenComplete函数后,可以再调用thencatchError等方法,但是后面注册回调函数并不能获取到whenComplete中返回的值,而是whenComplete前的值。 该逻辑等效于下面的代码

上代码

Future<T> whenComplete(action()) {
  return this.then((v) {
    var f2 = action();
    if (f2 is Future) return f2.then((_) => v);
    return v
  }, onError: (e) {
    var f2 = action();
    if (f2 is Future) return f2.then((_) { throw e; });
    throw e;
  })

四.4、 timeout方法

Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()})

  • timeout方法创建一个新的Future对象,接收一个Duration类型的timeLimit参数来设置超时时间。
  • 如果原Future在超时之前完成,最终的结果就是该原Future的值;
  • 如果达到超时时间后还未完成,就会产生TimeoutException异常。
  • 该方法有一个onTimeout可选参数,如果设置了该参数,当发生超时时会调用该函数,该函数的返回值为Future的新的值,而不会产生TimeoutException

五、Future一些相对少用的方法

五.1、 foreach方法

Future.foreach(Iterable elements, FutureOr action(T element))

根据某个集合,创建一系列的Future,并且会按顺序执行这些Future。 比如下面的例子,根据{1,2,3}创建3个延迟对应秒数的Future。执行结果为1秒后打印1,再过2秒打印2,再过3秒打印3,总时间为6秒。

Future.forEach({1,2,3}, (num){
  return Future.delayed(Duration(seconds: num),(){print(num);});
});

五.2、 wait方法

Future.wait ( Iterable<Future> futures,{bool eagerError: false, void cleanUp(T successValue)})

用来等待多个future完成,并收集它们的结果。那这样的结果就有两种情况了:

  • 如果所有future都有正常结果返回:则future的返回结果是所有指定future的结果的集合
  • 如果其中一个future有error返回:则future的返回结果是第一个error的值。

比如下面的例子,也是创建3个延迟对应秒数的Future。结果是总时间过了3秒后,才输出[1, 2, 3]的结果。可以与上面的例子对比一下,一个是顺序执行多个Future,一个是异步执行多个Future。

var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
var future2 =
    new Future.delayed(new Duration(seconds: 2), () => 2);
var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);
Future.wait({future1,future2,future3}).then(print).catchError(print);
//运行结果: [1, 2, 3]

将future2和future3改为有error抛出,则future.wait的结果是这个future2的error值。

var future1 = new Future.delayed(new Duration(seconds: 1), () => 1); var future2 =new Future.delayed(new Duration(seconds: 2), () => throwthrow error2"); var future3 = new Future.delayed(new Duration(seconds: 3), () => throw “throw error3"); Future.wait({future1,future2,future3}).then(print).catchError(print); //运行结果: throw error2

五.3、 Future.any(futures)

返回的是第一个执行完成的future的结果,不会管这个结果是正确的还是error的。

例如下面的例子,使用Future.delayed()延迟创建了三个不同的Future,第一个完成返回的是延迟1秒的那个future的结果。

 Future
      .any([1, 2, 5].map(
          (delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
      .then(print)
      .catchError(print);


五.4、 Future.doWhile()

类似java中doWhile的用法,重复性地执行某一个动作,直到返回false或者Future,退出循环。

  • 使用场景:适用于一些需要递归操作的场景。

生成一个随机数进行等待,直到十秒之后,操作结束。

void futureDoWhile(){
  var random = new Random();
  var totalDelay = 0;
  Future
      .doWhile(() {
    if (totalDelay > 10) {
      print('total delay: $totalDelay seconds');
      return false;
    }
    var delay = random.nextInt(5) + 1;
    totalDelay += delay;
    return new Future.delayed(new Duration(seconds: delay), () {
      print('waited $delay seconds');
      return true;
    });
  })
      .then(print)
      .catchError(print);
}
//输出结果:
I/flutter (11113): waited 5 seconds
I/flutter (11113): waited 1 seconds
I/flutter (11113): waited 3 seconds
I/flutter (11113): waited 2 seconds
I/flutter (11113): total delay: 12 seconds
I/flutter (11113): null


五.5、microtask

Future.microtask(FutureOr computation())

创建一个在microtask队列运行的future。

这个会需要知道一下优先级 main > microtask > event

microtask会优先于event执行

Future((){
  print("Future event 1");
});
Future((){
  print("Future event 2");
});
Future.microtask((){
 print("microtask event");
});
//输出结果
//microtask event
//Future event 1
//Future event 2


整理至此。晚安

原先看一些文章,总是写得阅读感受费劲。 本文成文参考了一下两篇文章,现分别列出,感谢作者。


参考:

Flutter | 事件循环,Future
juejin.cn/post/702699…

[Flutter]Dart Future详解
juejin.cn/post/684490…