Dart【05】async和await简化异步

241 阅读4分钟

作用

asyncawait是用来简化异步代码的API,使用asyncawait可以轻松的写出类似同步代码的异步代码,函数会更加清晰易读。

使用简介

在学习使用asyncawait之前,你需要先掌握DartFuture类的使用,因为asyncawait本质上只是对Future类的简化。

学习asyncawait之前,我们先来看一段使用Future类的代码:

void main() {
  int num;
  getNum().then((value) {
    num = value;
    print(num);
  });
}
​
Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}
​

这段代码很简单,将异步函数getNum()返回的值赋值给num,打印num

现在我们使用asyncawait来简化上面的代码:

void main() async{
  int num;
  num = await getNum();
  print(num);
}
​
Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}
​

首先我们将main方法后加入async,使用async的目的就是告诉Dart我要在这里使用await了。

正常情况下getNum()函数返回的是一个Future对象,当我们在getNum()前加入await时,await getNum()获取到的是Future完成后返回的值,也就是上面代码中的int类型的1

如果将代码改成下面这样:

void main() async{
  int num;
  num = getNum();
  print(num);
}
​

代码会报错,因为getNum返回的类型是Future<int>

await关键字的另一个功能是阻塞线程,当程序遇到await时,代码会等待await修饰的方法执行完成之后,再执行await后的代码,就像同步代码那样。

继续拿上面的代码举例:

void main() async{
  int num;
  num = await getNum();
  print(num);
}
​
Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}

当程序执行到 num = await getNum()时会等待2s,等到getNum执行结束。

这就是如何在Dart中使用asyncawait的全部内容了。

异常处理

我们都知道Future类有一个catchError方法,能够帮助我们处理异常

示例如下:

Future(() {
  throw StateError('This is a Dart exception in Future.');
}).catchError((dynamic e, StackTrace stack) => print(e), test: (e) => true);

那么我们在使用asyncawait时该如何处理异常呢?

答案很简单,我们只需要像同步代码那样使用try,catch处理异常就可以了。

示例如下:

void main() async {
  try {
    int num;
    num = await getNum();
    print(num);
  } catch (e) {}
}
​
Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}

使用场景

场景一

学习了这么多asyncawait的用法,我们什么使用Future,什么时候使用async呢?

Dart官方建议使用async,因为代码可读性很高,而且可以避免Future的嵌套地狱,当你的Future链式调用很长,又加上一堆catchError,读起来让人很痛苦。

但是有一些情况我们必须使用Future来完成我们的工作。

当你想在一个函数里执行异步操作,但又不想该函数返回Future,你就需要使用Future.then(),就像下面的代码示例一样。

main() {
  int num;
  getNum().then((value) {
    num = value;
    print(num);
  });
}
​
Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}

使用future不会影响main方法的返回值类型

当我们使用async时:

main() async{
  int num;
  num = await getNum();
  print(num);
}
​
Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}

返回值为Future<dynamic>类型,是一个Future对象。

场景二

如果你在一个函数里有多个异步操作,但他们之间没有关联,不想让第二个函数等待第一个函数完成,使用Future是更好的选择,因为await会阻塞代码。

我们接着使用上面的代码示例来给你演示区别

这是使用Future的代码示例:

void main() {
  int num;
  getNum().then((value) {
    num = value;
    print(num);
  });
  print("同步代码");
}

Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}

输出如下:

同步代码 1

这是使用async的代码示例:

void main() async{
  int num;
  num = await getNum();
  print(num);
  print("同步代码");
}

Future<int> getNum() {
  return Future.delayed(Duration(seconds: 2), () => 1);
}

输出如下:

1 同步代码

async程序等待getNum运行结束,才运行下面的代码,所以先输出1,再输出同步代码

而使用Future链式调用的代码因为是异步的,所以先输出同步代码,等到getNum结束再输出1

这里涉及到DartEvent Loop(事件循环)及dart如何用单线程处理异步,你可以先记住结论await会阻塞代码,像同步程序那样。

await for

就像使用await处理Future一样,await for是用来处理Stream的。

使用for循环来处理Stream返回的数据,在Stream完成前,代码会一直被阻塞。

再学习await for之前,你应该掌握Dart中的Stream

代码示例:

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  //sum不断累加  直到Stream完成
  await for (var value in stream) {
    sum += value;
  }
  return sum;
}

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield i;
  }
}

main() async {
  //获取到Stream
  var stream = countStream(10);
  //调用方法
  var sum = await sumStream(stream);
  print(sum); // 55
}

countStream主要用来创建流,sumStream方法展示了如何使用await for

值得一提的是,使用await for处理Stream就像使用awaitFuture一样,都是等待异步操作完成,代码才继续向下执行。