dart:async
非同步程式設計經常使用回呼函式,但 Dart 提供了替代方案:Future 和 Stream 物件。Future 就像一個承諾,在未來某個時間提供結果。Stream 是一種取得值序列(例如事件)的方式。Future、Stream 和更多內容都在 dart:async 函式庫中 (API 參考文件)。
dart:async 函式庫適用於 Web 應用程式和命令列應用程式。若要使用它,請匯入 dart:async
import 'dart:async';
Future
#Future 物件在整個 Dart 函式庫中隨處可見,通常作為非同步方法傳回的物件。當 future 完成時,其值即可使用。
使用 await
#在您直接使用 Future API 之前,請考慮改用 await
。使用 await
運算式的程式碼可能比使用 Future API 的程式碼更容易理解。
請考慮以下函式。它使用 Future 的 then()
方法依序執行三個非同步函式,等待每個函式完成後再執行下一個函式。
void runUsingFuture() {
// ...
findEntryPoint()
.then((entryPoint) {
return runExecutable(entryPoint, args);
})
.then(flushThenExit);
}
使用 await 運算式的等效程式碼看起來更像同步程式碼
Future<void> runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}
一個 async
函式可以捕捉 Futures 的例外。例如
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// Handle the error...
}
有關使用 await
和相關 Dart 語言功能的更多資訊,請參閱非同步程式設計教學。
基本用法
#您可以使用 then()
來排程在 future 完成時執行的程式碼。例如,Client.read()
會傳回 Future,因為 HTTP 請求可能需要一段時間。使用 then()
可讓您在該 Future 完成且承諾的字串值可用時執行一些程式碼
httpClient.read(url).then((String result) {
print(result);
});
使用 catchError()
來處理 Future 物件可能擲回的任何錯誤或例外。
httpClient
.read(url)
.then((String result) {
print(result);
})
.catchError((e) {
// Handle or ignore the error.
});
then().catchError()
模式是非同步版本的 try
-catch
。
鏈結多個非同步方法
#then()
方法會傳回 Future,提供一種以特定順序執行多個非同步函式的實用方法。如果使用 then()
註冊的回呼傳回 Future,則 then()
會傳回一個 Future,該 Future 將以與從回呼傳回的 Future 相同的結果完成。如果回呼傳回任何其他類型的值,則 then()
會建立一個新的 Future,該 Future 會以該值完成。
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* Handle exception... */
});
在前面的範例中,方法會依以下順序執行
costlyQuery()
expensiveWork()
lengthyComputation()
這是使用 await 撰寫的相同程式碼
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* Handle exception... */
}
等待多個 futures
#有時您的演算法需要調用許多非同步函式,並等待它們全部完成後才能繼續。使用 Future.wait()
靜態方法來管理多個 Futures 並等待它們完成
Future<void> deleteLotsOfFiles() async => ...
Future<void> copyLotsOfFiles() async => ...
Future<void> checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
Future.wait()
會傳回一個 future,一旦所有提供的 futures 都完成,它就會完成。它會以它們的結果完成,或者如果任何提供的 futures 失敗,則會以錯誤完成。
處理多個 futures 的錯誤
#您也可以等待 futures 的可迭代物件或記錄上的並行運算。
這些擴充功能會傳回一個 future,其中包含所有提供的 futures 的結果值。與 Future.wait
不同,它們也讓您可以處理錯誤。
如果集合中的任何 future 以錯誤完成,wait
會以 ParallelWaitError
完成。這允許呼叫者處理個別錯誤,並在必要時處置成功的結果。
當您不需要來自每個個別 future 的結果值時,請在 futures 的可迭代物件上使用 wait
void main() async {
Future<void> delete() async => ...
Future<void> copy() async => ...
Future<void> errorResult() async => ...
try {
// Wait for each future in a list, returns a list of futures:
var results = await [delete(), copy(), errorResult()].wait;
} on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
print(e.values[0]); // Prints successful future
print(e.values[1]); // Prints successful future
print(e.values[2]); // Prints null when the result is an error
print(e.errors[0]); // Prints null when the result is successful
print(e.errors[1]); // Prints null when the result is successful
print(e.errors[2]); // Prints error
}
}
當您需要來自每個 future 的個別結果值時,請在 futures 的記錄上使用 wait
。這提供了額外的好處,即 futures 可以是不同的類型
void main() async {
Future<int> delete() async => ...
Future<String> copy() async => ...
Future<bool> errorResult() async => ...
try {
// Wait for each future in a record, returns a record of futures:
(int, String, bool) result = await (delete(), copy(), errorResult()).wait;
} on ParallelWaitError<(int?, String?, bool?),
(AsyncError?, AsyncError?, AsyncError?)> catch (e) {
// ...
}
// Do something with the results:
var deleteInt = result.$1;
var copyString = result.$2;
var errorBool = result.$3;
}
Stream
#Stream 物件在整個 Dart API 中隨處可見,代表資料序列。例如,HTML 事件(例如按鈕點擊)是使用 streams 傳遞的。您也可以將檔案讀取為 stream。
使用非同步 for 迴圈
#有時您可以使用非同步 for 迴圈 (await for
) 而不是使用 Stream API。
請考慮以下函式。它使用 Stream 的 listen()
方法訂閱檔案清單,傳入一個函式常值,用於搜尋每個檔案或目錄。
void main(List<String> arguments) {
// ...
FileSystemEntity.isDirectory(searchPath).then((isDir) {
if (isDir) {
final startingDir = Directory(searchPath);
startingDir.list().listen((entity) {
if (entity is File) {
searchFile(entity, searchTerms);
}
});
} else {
searchFile(File(searchPath), searchTerms);
}
});
}
使用 await 運算式的等效程式碼,包括非同步 for 迴圈 (await for
),看起來更像同步程式碼
void main(List<String> arguments) async {
// ...
if (await FileSystemEntity.isDirectory(searchPath)) {
final startingDir = Directory(searchPath);
await for (final entity in startingDir.list()) {
if (entity is File) {
searchFile(entity, searchTerms);
}
}
} else {
searchFile(File(searchPath), searchTerms);
}
}
有關使用 await
和相關 Dart 語言功能的更多資訊,請參閱非同步程式設計教學。
監聽 stream 資料
#若要在每個值到達時取得它,請使用 await for
或使用 listen()
方法訂閱 stream
// Add an event handler to a button.
submitButton.onClick.listen((e) {
// When the button is clicked, it runs this code.
submitData();
});
在此範例中,onClick
屬性是由提交按鈕提供的 Stream
物件。
如果您只關心一個事件,您可以使用 first
、last
或 single
等屬性來取得它。若要在處理事件之前測試事件,請使用 firstWhere()
、lastWhere()
或 singleWhere()
等方法。
如果您關心事件的子集,您可以使用 skip()
、skipWhile()
、take()
、takeWhile()
和 where()
等方法。
轉換 stream 資料
#通常,您需要在使用 stream 的資料之前變更其格式。使用 transform()
方法來產生具有不同資料類型的 stream
var lines = inputStream
.transform(utf8.decoder)
.transform(const LineSplitter());
此範例使用兩個轉換器。首先,它使用 utf8.decoder 將整數 stream 轉換為字串 stream。然後,它使用 LineSplitter 將字串 stream 轉換為個別行的 stream。這些轉換器來自 dart:convert 函式庫 (請參閱 dart:convert 章節)。
處理錯誤和完成
#您如何指定錯誤和完成處理程式碼取決於您是否使用非同步 for 迴圈 (await for
) 或 Stream API。
如果您使用非同步 for 迴圈,請使用 try-catch 來處理錯誤。在 stream 關閉後執行的程式碼會放在非同步 for 迴圈之後。
Future<void> readFileAwaitFor() async {
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();
var lines = inputStream
.transform(utf8.decoder)
.transform(const LineSplitter());
try {
await for (final line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
}
如果您使用 Stream API,請透過註冊 onError
監聽器來處理錯誤。在 stream 關閉後執行程式碼,方法是註冊 onDone
監聽器。
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();
inputStream
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(
(String line) {
print('Got ${line.length} characters from stream');
},
onDone: () {
print('file is now closed');
},
onError: (e) {
print(e);
},
);
更多資訊
#有關在命令列應用程式中使用 Future 和 Stream 的一些範例,請查看 dart:io 文件。另請參閱以下文章和教學
除非另有說明,否則本網站上的文件反映 Dart 3.7.1。頁面最後更新於 2025-02-12。 檢視原始碼 或 回報問題。