Dart異步編程包含兩部分:Future和Stream
該篇文章中介紹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的函數被調用的時候,做了兩件事情:
1. 函數把自己放入隊列和返回一個未完成的Future對象
2. 之后當值可用時,Future帶着值變成完成狀態。
為了獲得Future的值,有兩種方式:
1. 使用async和await
2. 使用Future的接口
Async和await
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需要消耗一定時間的執行完成,而不會影響功能:用戶在讀取新聞之前獲得其他消息。
下面的圖展示代碼的執行流程。每一個數字對應着相應的步驟
1. 開始程序執行
2. main函數調用printDailyNewsDigest,因為它被標記為async,所有在該函數任何代碼被執行之前立即返回一個Future。
3. 剩下的打印執行。因為它們是同步的。所有只有當一個打印函數執行完成過后才能執行下一個打印函數。例如:中獎號碼在天氣預報執行打印。
4. 函數printDailyNewsDigest函數體開始執行
5. 在到達await之后,調用gatherNewsReports,程序暫停,等待gatherNewsReports返回的Future完成。
6. 當Future完成,printDailyNewsDigest繼續執行,打印新聞。
7. 當printDailyNewsDigest執行完成過后,最開始的Future返回完成,程序退出。
注:如果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被首先調用,但是新聞在最后被打印。這是因為代碼讀取內容是異步執行的。
程序執行流程:
1. 程序開始執行
2. main函數調用printDailyNewsDigest函數,在函數內部調用gatherNewsReports
3. gatherNewsReports收集新聞然后返回一個Future
4. printDailyNewsDigest使用then指定Future的響應函數。調用then()返回的新Future將使用then指定回調返回的值完成。
5. 剩下的打印函數同步執行,因為他們是同步的
6. 當所有新聞到達,gatherNewsReports返回的Future帶着一個包含新聞的字符串完成
7. 在printDailyNewsDigest中then函數指定的回調函數執行。打印新聞
8. 程序退出
在printDailyNewsDigest函數中then可以多種不同的方式寫。
1. 使用花括號。當有多個操作的時候,這種方式比較方便
printDailyNewsDigest() { final future = gatherNewsReports(); future.then((news) { print(news); // Do something else... }); }
2. 直接傳print函數
printDailyNewsDigest() =>
gatherNewsReports().then(print);
錯誤處理
使用Future接口,你可以使用catchError捕獲錯誤
printDailyNewsDigest() => gatherNewsReports() .then((news) => print(news)) .catchError((e) => handleError(e));
如果新聞不可用,那么代碼的執行流程如下:
1. gatherNewsReports的Future帶一個Error完成。
2. then的Future帶一個Error完成。
3. 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處理錯誤
參考: https://www.dartlang.org/tutorials/language/futures