重點
- Dart程式碼執行在一個已執行的執行緒內。
- 阻塞執行緒執行的程式碼能夠使程式凍結。
- Future物件(Futures)表示非同步執行的結果(將要完成的執行結果或I/O操作)。
- 非同步函式使用await()(或者用then()),暫停執行直到future完成。
- 非同步函式使用try-cache表示式,捕獲錯誤。
- 構造一個isolate(web 使用 worker),立刻執行程式碼,
Dart程式碼執行在一個已執行的執行緒內。如果Dart程式碼阻塞,例如,執行一個長時間運算或者等待I/O操作,程式將要凍結。非同步操作可以讓你在等待一個操作完成時,進行其他工作。Dart使用Future物件(Futures)表示非同步執行結果。你可以使用async和await也可以使用Future API,進行futures開發。
Node: 所有Dart程式碼執行在isolate上下文中,這個isolate擁有Dart程式碼所有記憶體。當Dart程式碼執行時,isolate內不能執行其他程式碼。如果你想多部分Dart程式碼同時執行,你能執行他們在其他的isolate(Web 用workers代替 isolate)。多個isolate同時執行,通常執行在各自的CPU核心上。isolate不共享記憶體,他們之間的唯一溝通方式是傳送訊息。關於isolate更多內容,請檢視文件isolates或者web workers
介紹
讓我們看一些可能導致程式凍結的程式碼:
// Synchronous codevoid 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複製程式碼
我們的程式碼是有潛在問題的:由於gatherNewsReports()阻塞,餘下的程式碼只能一直等待gatherNewsReports()從檔案讀取返回值之後執行。如果讀取檔案花費很長時間,使用者必須等,儘管使用者可能更想知道他們是否贏了彩票,明天的天氣是什麼,誰贏了今天的比賽。
為了保證程式響應,Dart庫作者處理耗時操作時使用非同步模式,這些函式使用future作為返回值。
#Future 是什麼?future是一個Future物件,它表示一個非同步操作產生一個T型別的結果。如果結果為不可用的值,返回型別為Future。當一個返回futrue的函式被呼叫時,有兩件事發生:
- 該函式將加入工作佇列,並返回一個未完成的Future物件。
- 之後,當操作完成後,返回完成的Future物件值或者錯誤。
使用future,有兩種方式:
- 用async 和 await
- 用Future API
Async 和 await
async和await是Dart支援非同步程式設計的一部分。他們允許你寫非同步程式碼,看起來像同步程式碼並且不需要使用Future API。非同步函式即是將async關鍵字放在函式體前即可。await關鍵字只用在async函式。下面程式使用async和await模擬從 www.dartlang.org. 讀取內容:
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file// for details. All rights reserved. Use of this source code is governed by a// BSD-style license that can be found in the LICENSE file.import 'dart:async';
Future<
void>
printDailyNewsDigest() async {
var newsDigest = await gatherNewsReports();
print(newsDigest);
}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);
// Alternatively, you can get news from a server using features// from either dart:io or dart:html. For example://// import 'dart:html';
//// Future<
String>
gatherNewsReportsFromServer() =>
HttpRequest.getString(// 'https://www.dartlang.org/f/dailyNewsDigest.txt',// );
複製程式碼
執行結果:
Winning lotto numbers: [23, 63, 87, 26, 2]Tomorrow's forecast: 70F, sunny.Baseball score: Red Sox 10, Yankees 0<
gathered news goes here>
複製程式碼
printDailyNewsDigest()是第一個被呼叫的,雖然只是輸出一行,但是新聞也是最後被列印的。這是因為執行和列印程式碼是非同步執行的。
在這個例子中,printDailyNewsDigest()呼叫非阻塞gatherNewsReports(),呼叫gatherNewsReports()會將執行任務加入佇列,但不會阻止剩下程式碼執行。程式列印彩票號碼,預測棒球比賽分數。當gatherNewsReports() 完成獲得新聞後,列印它。如果gatherNewsReports()花費一些時間完成,由於非同步執行也不會給使用者帶來很大的影響。在列印每日新聞之前使用者可以閱讀其他訊息。
請留意返回型別, gatherNewsReports()的返回型別是Future,這意味這個返回值是一個以字元future。printDailyNewsDigest() 無返回值,它的返回型別為Future。
下圖展示呼叫流程,數字和步驟相互對應:
- 程式開始執行
- main函式呼叫printDailyNewsDigest(),printDailyNewsDigest()開始執行。
- printDailyNewsDigest()使用await呼叫atherNewsReports(),atherNewsReports()開始執行。
- gatherNewsReports()返回一個未完成的future(一個Future例項)。
- 因為printDailyNewsDigest()是一個非同步函式並且等待返回值,它暫停執行並返回一個未完成的future(這種情況下,返回的是Future)給它的呼叫者(這裡是main函式)。
- 執行剩下的列印函式。因為他們是同步的,每個函式完全執行完,才會執行下一個函式。例如,彩票中獎號碼都是在天氣預報之前列印出來的。
- 當main()函式完成執行,非同步函式仍然能執行。首先gatherNewsReports() 完成後返回future,然後 printDailyNewsDigest()繼續執行,列印新聞。
- 當printDailyNewsDigest()函式體完成執行,完成的future返回,程式退出。
注意,非同步函式立即(同步地)開始執行。當第一次出現一下情況時,函式暫停執行,並返回一個未完成的future:
- 函式的第一個await表示式(函式獲得未完成future之後)。
- reture 語句。
- 函式體結束。
錯誤處理
非同步函式使用try-cache進行錯誤處理。
Future<
void>
printDailyNewsDigest() async {
try {
var newsDigest = await gatherNewsReports();
print(newsDigest);
} catch (e) {
// Handle error...
}
}複製程式碼
try-cache的行為在非同步程式碼和同步程式碼中是相同的,如果try塊中的程式碼丟擲異常,則catch子句中的程式碼將執行。
順序處理
您可以使用多個await表示式來確保順序執行,即每個語句在執行下一個語句之前完成:
// Sequential processing using async and await.main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC());
}複製程式碼
expensiveA()執行完成後,才會執行expensiveB().
#Future API在Dart 1.9中新增async和await之前,您必須使用Future API。目前仍然可能在老的Dart程式碼中以及需要比asyn-await提供更豐富功能的程式碼中,看到Future API。
使用Future API寫非同步程式碼,用then()註冊回撥。當Future完成後,回撥被執行。
下面程式使用Future API模擬從 www.dartlang.org. 讀取內容:
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file// for details. All rights reserved. Use of this source code is governed by a// BSD-style license that can be found in the LICENSE file.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);
// Alternatively, you can get news from a server using features// from either dart:io or dart:html. For example://// import 'dart:html';
//// Future<
String>
gatherNewsReportsFromServer() =>
HttpRequest.getString(// 'https://www.dartlang.org/f/dailyNewsDigest.txt',// );
複製程式碼
結果輸出:
Winning lotto numbers: [23, 63, 87, 26, 2]Tomorrow's forecast: 70F, sunny.Baseball score: Red Sox 10, Yankees 0<
gathered news goes here>
複製程式碼
printDailyNewsDigest()是第一個被呼叫的,雖然只是輸出一行,但是新聞也是最後被列印的。這是因為執行和列印程式碼是非同步執行的。
程式執行步驟:
- 開始執行
- main()函式呼叫printDailyNewsDigest(),printDailyNewsDigest()沒有立即返回,而是呼叫了gatherNewsReports().
- gatherNewsReports()開始獲取新聞並且返回一個Future。
- printDailyNewsDigest()使用then()處理對應的Future返回值。呼叫then()返回一個新的Future,它將作為then()的回撥引數。
- 執行剩下的列印函式。因為他們是同步的,每個函式完全執行完,才會執行下一個函式。例如,彩票中獎號碼都是在天氣預報之前列印出來的。
- 當所有的新聞都收到後, gatherNewsReports()完成後返回包含新聞資訊字串的Future。
- 在printDailyNewsDigest()中指定的then()執行,列印新聞。
- 退出程式。
Node : 在printDailyNewsDigest()函式,future.then(print) 等價於:future.then((newsDigest) =>
print(newsDigest))
另外,then()內部程式碼可以使用{
}:
Future<
void>
printDailyNewsDigest() {
final future = gatherNewsReports();
return future.then((newsDigest) {
print(newsDigest);
// Do something else...
});
}複製程式碼
你需要提供一個引數給then()的回撥,即使Future是Future型別。按照慣例,一個無用引數使用下劃線表示。
final future = printDailyNewsDigest();
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);
複製程式碼
如果新聞資料讀取無效,程式碼的執行流程如下:
- gatherNewsReports()返回包含錯誤資訊的future。
- then()返回的future以錯誤結束,print()函式不被掉用。
- catchError() (handleError())處理錯誤, catchError()返回的正常的future,並且錯誤不會被傳播。
鏈式模式是Future API的常見模式。可以將Future API 等同於try-catch模組。
與then()類似,catchError()返回一個新的Future,它是回撥的返回值。更多詳細資訊和例子,請參考Futures and Error Handling.
呼叫多個函式
考慮三個函式,expensiveA(), expensiveB(), 和expensiveC(),這三個函式都返回Future物件。你能順序呼叫他們,或者你能同時開始他們,並在所有函式執行完成後做一些事情。Future介面可以輕鬆的處理這兩種用例。
使用then()進行鏈式呼叫
當函式需要按順序執行時,使用鏈式then():
expensiveA() .then((aValue) =>
expensiveB()) .then((bValue) =>
expensiveC()) .then((cValue) =>
doSomethingWith(cValue));
複製程式碼
巢狀回撥雖然可以工作,但是難於閱讀。
使用Future.wait()等待多個future完成
如果函式的執行順序不重要,你可以用Future.wait()。
當你傳遞一個future列表給Future.wait(),它立刻返回一個未完成的future。直到給定的future列表全部執行完這個future才完成。future的返回值,由列表中的每個future的返回值組成。
Future.wait([expensiveA(), expensiveB(), expensiveC()]) .then((List responses) =>
chooseBestResponse(responses, moreInfo)) .catchError(handleError);
複製程式碼
如果任何一個函式返回錯誤,Future.wait()的futrue都以錯誤結束。使用catchError()處理錯誤。
#其他資源閱讀以下文件,瞭解有關在Dart中使用future和非同步程式設計的更多詳細資訊:
- Futures and Error Handling
- The Event Loop and Dart
- Asynchrony support
- API 文件API futures, isolates, web workers.