關鍵點
- 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關鍵字
- 使用
Future
API
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()
捕獲到去處理錯誤。