Dart非同步程式設計之Future
Dart非同步程式設計包含兩部分:Future和Stream
本文將詳細介紹Future
Dart非同步程式設計-future
非同步程式設計:Futures
Dart是一個單執行緒程式語言。如果任何程式碼阻塞執行緒執行都會導致程式卡死。非同步程式設計防止出現阻塞操作。Dart使用Future物件表示非同步操作。
介紹
如下程式碼可能導致程式卡死
// Synchronous code printDailyNewsDigest() { String news = gatherNewsReports(); // Can take a while. print(news); } 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結束才能繼續執行。當用戶想知道自己是否中彩票,明天的天氣和誰贏得比賽,都必須等待printDailyNewsDigest讀取結束。
為了程式及時響應,Dart的作者使用非同步程式設計模型處理可能耗時的函式。這個函式返回一個Future
什麼是Future
Future表示在將來某時獲取一個值的方式。當一個返回Future的函式被呼叫的時候,做了兩件事情:
- 函式把自己放入佇列和返回一個未完成的Future物件
- 之後當值可用時,Future帶著值變成完成狀態。
為了獲得Future的值,有兩種方式: - 使用async和await
- 使用Future的介面
Async和await
async 和 await 是Dart async和await關鍵字是Dart非同步支援的一部分。他們允許你像寫同步程式碼一樣寫非同步程式碼和不需要使用Future介面。
注:在Dart2中有輕微的改動。async函式執行時,不是立即掛起,而是要執行到函式內部的第一個await。
在多數情況下,我們不會注意到這一改動
下面的程式碼使用Async和await讀取新聞
// 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';
printDailyNewsDigest() async {
String news = await gatherNewsReports();
print(news);
}
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>';
Duration oneSecond = const Duration(seconds: 1);
final newsStream = new Stream.periodic(oneSecond, (_) => news);
// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;
// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future 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需要消耗一定時間的執行完成,而不會影響功能:使用者在讀取新聞之前獲得其他訊息。
下面的圖展示程式碼的執行流程。每一個數字對應著相應的步驟
-
程式開始執行。
-
主函式呼叫asynchronousCode()函式。
-
asynchronousCode()函式呼叫printDailyNewsDigest_async()函式,然後函式立即返回一個Future 物件。
-
asynchronousCode()函式其它中接下來的程式碼依次開始執行。以此執行printWinningLotteryNumbers(),printWeatherForecast(),printBaseballScore()函式,並返回對應的結果。
-
printDailyNewsDigest_async()函式體開始執行。
-
當執行到await 語句時:程式暫停,等待gatherNewsReports_async()函式執行結果。
-
一旦gatherNewsReports_async()函式的Future 執行完成,printDailyNewsDigest_async()函式將繼續執行。
-
當printDailyNewsDigest_async()函式執行完成後,程式結束。
注:如果async函式沒有明確指定返回值,返回的null值的Future
錯誤處理
如果在Future返回函式發生錯誤,你可能想捕獲錯誤。Async函式可以用try-catch捕獲錯誤。
printDailyNewsDigest() async {
try {
String news = await gatherNewsReports();
print(news);
} catch (e) {
// Handle error...
}
}
try-catch在同步程式碼和非同步程式碼的表現是一樣的:如果在try塊中間發生異常,那麼在catch中的程式碼執行
連續執行
你可以使用多個await表示式,保證一個await執行完成過後再執行下一個
// Sequential processing using async and await.
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC());
}
expensiveB函式將等待expensiveA完成之後再執行。
Future API
在Dart1.9之前還沒有新增async和await時,你必須使用Future介面。你可能在一些老的程式碼中間看到使用Future介面
為了使用Future介面寫非同步程式碼,你需要使用then()方法註冊一個回撥。當Future完成時這個回撥被呼叫。
下面的程式碼使用Future介面:
// 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';
printDailyNewsDigest() {
final future = gatherNewsReports();
future.then((news) => print(news));
}
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>';
Duration oneSecond = const Duration(seconds: 1);
final newsStream = new Stream.periodic(oneSecond, (_) => news);
// Imagine that this function is more complex and slow. :)
Future gatherNewsReports() => newsStream.first;
// Alternatively, you can get news from a server using features
// from either dart:io or dart:html. For example:
//
// import 'dart:html';
//
// Future gatherNewsReportsFromServer() => HttpRequest.getString(
// 'https://www.dartlang.org/f/dailyNewsDigest.txt',
// );
注意到printDailyNewsDigest被首先呼叫,但是新聞在最後被列印。這是因為程式碼讀取內容是非同步執行的。
程式執行流程:
- 程式開始執行
- main函式呼叫printDailyNewsDigest函式,在函式內部呼叫gatherNewsReports
- gatherNewsReports收集新聞然後返回一個Future
- printDailyNewsDigest使用then指定Future的響應函式。呼叫then()返回的新Future將使用then指定回撥返回的值完成。
- 剩下的列印函式同步執行,因為他們是同步的
- 當所有新聞到達,gatherNewsReports返回的Future帶著一個包含新聞的字串完成
- 在printDailyNewsDigest中then函式指定的回撥函式執行。列印新聞
- 程式退出
在printDailyNewsDigest函式中then可以多種不同的方式寫。
- 使用花括號。當有多個操作的時候,這種方式比較方便
printDailyNewsDigest() {
final future = gatherNewsReports();
future.then((news) {
print(news);
// Do something else...
});
}
- 直接傳print函式
printDailyNewsDigest() =>
gatherNewsReports().then(print);
錯誤處理
使用Future介面,你可以使用catchError捕獲錯誤
printDailyNewsDigest() =>
gatherNewsReports()
.then((news) => print(news))
.catchError((e) => handleError(e));
如果新聞不可用,那麼程式碼的執行流程如下:
- gatherNewsReports的Future帶一個Error完成。
- then的Future帶一個Error完成。
- catchError的回撥處理錯誤,catchError的Future正常結束,錯誤不在被傳遞。
當使用Future介面時,在then後連線catchError,可以認為這一對介面相應於try-catch。
像then(), catchError返回一個帶回調返回值的新Future
使用then連線多個函式
當Future返回時需要順序執行多個函式是,可以串聯then呼叫
expensiveA()
.then((aValue) => expensiveB())
.then((bValue) => expensiveC())
.then((cValue) => doSomethingWith(cValue));
使用Future.wait等待多個Future完成
當wait的所有Future完成時,Future.wait返回一個新的Future。這個Future帶了一個包含所有等待結果的列表。
Future
.wait([expensiveA(), expensiveB(), expensiveC()])
.then((List responses) => chooseBestResponse(responses))
.catchError((e) => handleError(e));
當任意一個呼叫以error結束時,那麼Future.wait也以一個Error結束。使用catchError處理錯誤