0%

【文章翻譯】Dart asynchronous programming: Futures

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

Dart 非同步程式設計:Futures

許多非同步 Dart API 會返回 futures。

Dart 用於非同步程式設計的最基本 API 之一是 futures——Future 類型的物件。在大多數情況下,Dart 的 futures 與其他語言中的 futurepromise API 非常相似。

本文討論 Dart futures 背後的概念,並告訴您如何使用 Future API。它還討論了 Flutter FutureBuilder widget,它可以根據 future 的狀態,幫助您非同步更新 Flutter UI。

由於 Dart 語言功能(例如 async-await),您可能永遠不需要直接使用 Future API。但是您幾乎肯定會在 Dart 程式碼中遇到 futures。而且您可能想要 建立 futures 或 讀取 使用 Future API 的程式碼。

本文是基於 Flutter in Focus 影片系列 Dart 中的非同步程式設計 的第二篇文章。第一篇文章,隔離區和事件迴圈,涵蓋了 Dart 支援背景工作的基礎。

如果您喜歡透過觀看或聆聽來學習,本文中的所有內容都包含在以下影片中。

您可以將 futures 想像成資料的小禮盒。有人遞給您其中一個禮盒,一開始是關閉的。過了一會兒,盒子彈開了,裡面要麼是一個值,要麼是一個錯誤。

因此,future 可以處於以下三種狀態之一:

  1. 未完成: 禮盒是 關閉的
  2. 已完成並帶有值: 禮盒是打開的,您的禮物(資料)已 準備好
  3. 已完成並帶有錯誤: 禮盒是打開的,但 出現了問題

您即將看到的大部分程式碼都圍繞著處理這三種狀態。您收到一個 future,您需要決定在盒子打開之前做什麼,在盒子打開並帶有值時做什麼,以及如果出現錯誤時做什麼。您會經常看到這種 1-2-3 模式。

future 的 3 種狀態

您可能還記得我們關於 Dart 事件迴圈 的文章中的事件迴圈(如下圖所示)。關於 futures,需要了解的一件好事是,它們實際上只是一個 API,旨在使事件迴圈的使用更容易。

Dart 事件迴圈一次處理一個事件。

您編寫的 Dart 程式碼由單個執行緒執行。在您的應用程式執行的整個過程中,那個小小的執行緒一直不斷地循環,從事件佇列中提取事件並處理它們。

假設您有一些下載按鈕的程式碼(如下所示,實現為 RaisedButton)。使用者點擊,您的按鈕開始下載圖片。

首先發生點擊事件。事件迴圈獲取事件,並調用您的點擊處理程式(您使用 onPressed 參數設定給 RaisedButton 建構函式)。您的處理程式使用 http 函式庫發出請求 (http.get()),並返回一個 future (myFuture)。

因此,現在您有了您的小盒子 myFuture。它一開始是關閉的。要註冊一個回呼函式,以便在它打開時執行,您可以使用 then()

一旦您有了您的禮盒,您就等待。也許其他事件進來了,使用者做了一些事情,而您的小盒子就放在那裡,而事件迴圈繼續循環。

最終,圖片的資料被下載,http 函式庫說:「太好了!我這裡有這個 future。」它將資料放入盒子中並彈開它,這將觸發您的回呼函式。

現在,您傳遞給 then() 的那小段程式碼執行了,它顯示了圖像。

在整個過程中,您的程式碼從未需要直接接觸事件迴圈。其他正在發生的事情,或者其他進來的事件都無關緊要。您需要做的就是從 http 函式庫獲取 future,然後說明 future 完成後要做什麼。

在實際程式碼中,您還需要處理錯誤。我們稍後會向您展示如何做到這一點。

讓我們仔細看看 Future API,其中一些您剛剛在使用中看到了。

好的,第一個問題:您如何獲得 Future 的實例?在大多數情況下,您不會直接建立 futures。這是因為許多常見的非同步程式設計任務已經有為您產生 futures 的函式庫。

例如,網路通訊返回一個 future:

1
final myFuture = http.get('http://example.com');

存取共享偏好設定也會返回一個 future:

1
final myFuture = SharedPreferences.getInstance();

但是您也可以使用 Future 建構函式來建立 futures。

Future 建構函式

最簡單的建構函式是 Future(),它接受一個函式並返回一個與函式返回類型匹配的 future。稍後,函式會非同步運行,future 會以函式的返回值完成。以下是如何使用 Future() 的示例:

讓我們添加一些列印語句以使非同步部分更清楚:

如果您在 DartPad (dartpad.dev) 中運行該程式碼,則整個 main 函式會在傳遞給 Future() 建構函式的函式之前完成。這是因為 Future() 建構函式一開始只返回一個未完成的 future。它說:「這是這個盒子。您先拿著它,稍後我會去運行您的函式,並在其中放入一些資料。」以下是上述程式碼的輸出:

1
2
Done with main().
Creating the future.

另一個建構函式 Future.value() 在您已經知道 future 的值時很方便。當您構建使用快取的服務時,此建構函式非常有用。有時您已經擁有所需的值,因此您可以將其放入其中:

1
final myFuture = Future.value(12);

Future.value() 建構函式有一個用於完成並帶有錯誤的對應項。它被稱為 Future.error(),它的工作原理基本相同,但接受一個錯誤物件和一個可選的堆疊追蹤:

1
final myFuture = Future.error(ArgumentError.notNull('input'));

最方便的 future 建構函式可能是 Future.delayed()。它的工作原理與 Future() 類似,只是它會在運行函式和完成 future 之前等待指定的時長。

使用 Future.delayed() 的一種方法是當您為測試建立模擬網路服務時。如果您需要確保您的載入微調器正確顯示,則延遲的 future 是您的朋友。

使用 futures

現在您知道了 futures 的來源,讓我們來談談如何使用它們。正如我們前面提到的,使用 future 主要與處理它的三種狀態有關:未完成,已完成並帶有值,或已完成並帶有錯誤。

以下程式碼使用 Future.delayed() 建立一個 future,該 future 在 3 秒後完成,值為 100。

當此程式碼執行時,main() 從上到下運行,建立 future 並列印「Waiting for a value…」。在整個過程中,future 都是未完成的。它在 3 秒後才會完成。

使用 完成的值,您可以使用 then()。這是每個 future 上的一個實例方法,您可以使用它來註冊一個回呼函式,以便在 future 完成並帶有值時執行。您給它一個接受單個參數的函式,該參數與 future 的類型匹配。一旦 future 完成並帶有值,您的函式將使用該值被調用。

以下是上述程式碼的輸出:

1
2
Waiting for a value... _(3 秒後回呼函式執行)_
The value is 100.

除了執行您的程式碼外,then() 還會返回一個它自己的 future,與您傳遞給它的任何函式的返回值匹配。因此,如果您需要進行幾次非同步調用,即使它們的返回類型不同,您也可以將它們鏈接在一起。

回到我們的第一個示例,如果初始 future 沒有完成並帶有值 - 如果它完成並帶有錯誤會發生什麼?then() 方法需要一個值。您需要一種方法來註冊另一個回呼函式以防出現錯誤。

答案是使用 catchError()。它的工作原理與 then() 類似,只是它接受一個錯誤而不是一個值,並且如果 future 完成並帶有錯誤,它就會執行。就像 then() 一樣,catchError() 方法也返回它自己的 future,因此您可以構建一個由 then()catchError() 方法組成的完整鏈,它們彼此等待。

注意:如果您使用 async-await 語言功能,則無需調用 then()catchError()。相反,您 await 完成的值,並使用 try-catch-finally 來處理錯誤。有關詳細資訊,請參閱 Dart 語言導覽的 非同步支援 部分。

以下是如何使用 catchError() 來處理 future 完成並帶有錯誤的情況的示例:

您甚至可以給 catchError() 一個測試函式來檢查錯誤,然後再調用回呼函式。您可以透過這種方式擁有多個 catchError() 函式,每個函式檢查不同類型的錯誤。以下是如何使用 catchError() 的可選 test 參數指定測試函式的示例:

現在您已經了解到這裡,希望您可以看到 future 的三種狀態通常如何反映在程式碼的結構中。上例中有三個程式碼塊:

  1. 第一個程式碼塊建立一個未完成的 future。
  2. 然後,如果 future 完成並帶有值,則會有一個函式被調用。
  3. 然後,如果 future 完成並帶有錯誤,則會有一個函式被調用。

還有一個您可能想要使用的方法:whenComplete()。您可以使用它在 future 完成時執行一個函式,無論它是帶有值還是錯誤。

它有點像 try-catch-finally 中的 finally 程式碼塊。如果一切順利,則會執行程式碼;如果有錯誤,則會執行程式碼;無論如何都會執行程式碼。

在 Flutter 中使用 futures

這就是您如何建立 futures,以及一些關於如何使用它們的值的資訊。現在讓我們來談談如何在 Flutter 中使用它們。

假設您有一個網路服務將返回一些 JSON 資料,並且您想要顯示該資料。您可以建立一個 StatefulWidget 來建立 future,檢查完成或錯誤,調用 setState(),並通常手動處理所有連線。

或者您可以使用 FutureBuilder。它是 Flutter SDK 附帶的一個 widget。您給它一個 future 和一個 builder 函式,它會在 future 完成時自動重建其子項。

FutureBuilder widget 的工作原理是調用其 builder 函式,該函式接受一個上下文和 future 當前狀態的快照。

您可以檢查快照以查看 future 是否完成並帶有錯誤:

否則,您可以檢查 hasData 屬性以查看它是否完成並帶有值:

如果 hasErrorhasData 都不是 true,那麼您就知道您仍在等待,您也可以為此輸出一些內容。

即使在 Flutter 程式碼中,您也可以看到這三種狀態如何不斷出現:未完成,已完成並帶有值,以及已完成並帶有錯誤。

總結

本文討論了 futures 代表什麼,以及如何使用 Future 和 FutureBuilder API 來建立 futures 並使用它們的完成值。

如果您想了解更多關於使用 futures 的資訊 - 可以使用可運行的示例和互動式練習來測試您的理解 - 請查看關於 futures、async 和 await 的非同步程式碼實驗室。

或者繼續觀看 Dart 中的非同步程式設計 系列中的下一個影片。它討論了 streams,它們與 futures 非常相似,因為它們可以提供值或錯誤。但是,futures 只給您一個結果就停止了,而 streams 則繼續運行。

非常感謝 Andrew Brogdon,他製作了本文所依據的影片。


Dart 非同步程式設計:Futures 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。