关键点
- dart是单线程语言
- 同步代码会阻塞你的程序
- 使用
Future对象来执行异步操作 - 在async函数里使用
await关键字来挂起执行知道一个Future操作完成 - 或者使用
then()方法 - 在async函数里使用try-catch表达式捕获错误
- 或者使用
catchError()方法 - 可以使用链式操作future对象来按顺序执行异步函数
dart是一个单线程的编程语言,如果编写了任何阻塞执行线程的代码(例如耗时计算或者I/O),程序就会被阻塞。异步操作可以让你在等待一个操作完成的同时完成其他工作。Dart使用Future对象来进行异步操作。
介绍
先来看一个会导致阻塞的程序代码:
// Synchronous code
void printDailyNewsDigest() {
var newsDigest = gatherNewsReports(); // Can take a while.
print(newsDigest);
}
main() {
printDailyNewsDigest();
printWinningLotteryNumbers();
printWeatherForecast();
printBaseballScore();
}
程序会收集当天的新闻并且打印出来(耗时操作),然后打印一些用户感兴趣的其他信息。
<gathered news goes here>
Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny.
Baseball score: Red Sox 10, Yankees 0
这段代码是有问题的,当printDailyNewsDigest()方法做耗时操作阻塞,剩下的其他代码无论多长时间都只有等到printDailyNewsDigest()返回结果之后才能继续执行。
为了帮助保持应用程序的响应,Dart库的作者在定义可能做耗时工作的函数时用了异步模型。这些函数的返回值是一个future。
future是什么
一个future是一个Future的泛型Future<T>对象,代表了一个异步操作产生的T类型的结果。如果结果的值不可用,future的类型会是Future<void>,当返回一个future的函数被调用了,将会发生如下两件事:
1.这个函数加入待完成的队列并且返回一个未完成的Future对象。
2.接着,当这个操作结束了,Future对象返回一个值或者错误。
编写返回一个future的代码时,有两面两个可选方法:
- 使用async和await关键字
- 使用
FutureAPI
async 和 await
async 和 await 关键字是Dart语言异步支持的一部分。允许你不使用Future api像编写同步代码一样编写异步代码。一个异步函数的函数体前面要生命async关键字,await关键字仅在异步函数里生效。
下面的代码就是一个使用了Future api的例子。
import 'dart:async';
Future<void> printDailyNewsDigest() {
final future = gatherNewsReports();
return future.then(print);
// You don't *have* to return the future here.
// But if you don't, callers can't await it.
}
main() {
printDailyNewsDigest();
printWinningLotteryNumbers();
printWeatherForecast();
printBaseballScore();
}
printWinningLotteryNumbers() {
print('Winning lotto numbers: [23, 63, 87, 26, 2]');
}
printWeatherForecast() {
print("Tomorrow's forecast: 70F, sunny.");
}
printBaseballScore() {
print('Baseball score: Red Sox 10, Yankees 0');
}
const news = '<gathered news goes here>';
const oneSecond = Duration(seconds: 1);
// Imagine that this function is more complex and slow. :)
Future<String> gatherNewsReports() =>
Future.delayed(oneSecond, () => news);
在main()方法里面printDailyNewsDigest()是一个被调用的,但是因为等待了一秒延迟执行,所以在最后才被打印出来。这个程序的执行顺序如下:
1.程序开始执行
2.main方法调用printDailyNewsDigest(),但是不会立即返回,会调用gatherNewsReports()
3.gatherNewsReports()开始收集新闻同时返回一个Future。
4.printDailyNewsDigest()使用then()指定一个Future的响应结果,调用then()返回一个新完成的Future结果作为他的回调。
5.剩下的print方法是同步的会依次执行。
6.当所有的新闻都被收集到了,带有新闻信息的Future被gatherNewsReports()返回。
7.then()方法执行,将future返回的String作为参数传递给print打印。
future.then(print)等同于future.then((newsDigest) => print(newsDigest))
所以printDailyNewsDigest()还可以写成:
Future<void> printDailyNewsDigest() {
final future = gatherNewsReports();
return future.then((newsDigest) {
print(newsDigest);
// Do something else...
});
}
newsDigest是gatherNewsReports()返回的结果。
即时这个Future的类型是Future<void>,也需要传递一个参数给then()的回调,直接用下划线_表示。
Future<void> printDailyNewsDigest() {
final future = gatherNewsReports();
return future.then((_) {
// Code that doesn't use the `_` parameter...
print('All reports printed.');
});
}
处理错误
在Future api里,可以使用catchError()捕获错误:
Future<void> printDailyNewsDigest() =>
gatherNewsReports().then(print).catchError(handleError);
如果请求不到新闻并且失败了,上面的代码将会执行:
1.gatherNewsReports() 完成返回的future携带了error
2.then()方法中的print不会被执行
3.catchError()捕获到处理错误,future被catchError()正常完成并返回,错误不会继续传递下去。
更多细节阅读 Futures and Error Handling
调用多个函数返回futures
有三个函数,expensiveA()、 expensiveB() 和 expensiveC(), 它们都返回Future对象。你可以顺序调用它们(one by one),或者也可以同时调用三个函数并且当所有结果都返回时再做处理。Future api支持以上的操作。
用then()进行链式调用
按顺序使用then()调用每个方法
expensiveA()
.then((aValue) => expensiveB())
.then((bValue) => expensiveC())
.then((cValue) => doSomethingWith(cValue));
这是个嵌套调用,上一个函数的返回结果作为下一个函数的参数。
使用Future.wait()等待多个方法一起完成
如果好几个函数的执行顺序无关紧要,可以使用Future.wait()。
Future.wait([expensiveA(), expensiveB(), expensiveC()])
.then((List responses) => chooseBestResponse(responses, moreInfo))
.catchError(handleError);
当传递一个future列表给Future.wait()时,它会立即返回一个Future,但是直到列表里的future都完成的时候,这个Future才会完成,他会返回一个列表里面是每一个future产生的结果数据。
如果任何一个调用的函数产生了错误,都会被catchError()捕获到去处理错误。