Flutter非同步程式設計: Futures

天涯不歸客發表於2018-12-27

重點

  • 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的函式被呼叫時,有兩件事發生:

  1. 該函式將加入工作佇列,並返回一個未完成的Future物件。
  2. 之後,當操作完成後,返回完成的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。

下圖展示呼叫流程,數字和步驟相互對應:

Flutter非同步程式設計: Futures
  1. 程式開始執行
  2. main函式呼叫printDailyNewsDigest(),printDailyNewsDigest()開始執行。
  3. printDailyNewsDigest()使用await呼叫atherNewsReports(),atherNewsReports()開始執行。
  4. gatherNewsReports()返回一個未完成的future(一個Future例項)。
  5. 因為printDailyNewsDigest()是一個非同步函式並且等待返回值,它暫停執行並返回一個未完成的future(這種情況下,返回的是Future)給它的呼叫者(這裡是main函式)。
  6. 執行剩下的列印函式。因為他們是同步的,每個函式完全執行完,才會執行下一個函式。例如,彩票中獎號碼都是在天氣預報之前列印出來的。
  7. 當main()函式完成執行,非同步函式仍然能執行。首先gatherNewsReports() 完成後返回future,然後 printDailyNewsDigest()繼續執行,列印新聞。
  8. 當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()是第一個被呼叫的,雖然只是輸出一行,但是新聞也是最後被列印的。這是因為執行和列印程式碼是非同步執行的。

程式執行步驟:

  1. 開始執行
  2. main()函式呼叫printDailyNewsDigest(),printDailyNewsDigest()沒有立即返回,而是呼叫了gatherNewsReports().
  3. gatherNewsReports()開始獲取新聞並且返回一個Future。
  4. printDailyNewsDigest()使用then()處理對應的Future返回值。呼叫then()返回一個新的Future,它將作為then()的回撥引數。
  5. 執行剩下的列印函式。因為他們是同步的,每個函式完全執行完,才會執行下一個函式。例如,彩票中獎號碼都是在天氣預報之前列印出來的。
  6. 當所有的新聞都收到後, gatherNewsReports()完成後返回包含新聞資訊字串的Future。
  7. 在printDailyNewsDigest()中指定的then()執行,列印新聞。
  8. 退出程式。

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);
複製程式碼

如果新聞資料讀取無效,程式碼的執行流程如下:

  1. gatherNewsReports()返回包含錯誤資訊的future。
  2. then()返回的future以錯誤結束,print()函式不被掉用。
  3. 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和非同步程式設計的更多詳細資訊:

#原文連結Asynchronous Programming: Futures

來源:https://juejin.im/post/5c2447ee51882546b241a8a4

相關文章