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 函式可以捕獲 Future 丟擲的異常。例如
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... */
}等待多個 Future
#有時您的演算法需要呼叫許多非同步函式並等待它們全部完成後才能繼續。使用 Future.wait() 靜態方法來管理多個 Future 並等待它們完成
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,該 Future 在所有提供的 Future 完成後完成。它要麼帶著它們的結果完成,要麼在任何提供的 Future 失敗時帶著錯誤完成。
處理多個 Future 的錯誤
#您還可以等待並行操作
這些擴充套件返回一個 Future,其中包含所有提供的 Future 的結果值。與 Future.wait 不同,它們還允許您處理錯誤。
如果集合中的任何 Future 完成時出現錯誤,wait 會以 ParallelWaitError 完成。這允許呼叫者處理單個錯誤並在必要時處置成功的結果。
當您不需要每個 Future 的結果值時,請在 Future 的可迭代物件上使用 wait
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
void main() 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 的單個結果值時,請在 Future 的記錄上使用 wait。這提供了額外的優勢,即 Future 可以是不同型別的
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
void main() async {
try {
// Wait for each future in a record.
// Returns a record of futures that you can destructure.
final (deleteInt, copyString, errorBool) =
await (delete(), copy(), errorResult()).wait;
// Do something with the results...
} on ParallelWaitError<
(int?, String?, bool?),
(AsyncError?, AsyncError?, AsyncError?)
> catch (e) {
// ...
}
}Stream
#Stream 物件在 Dart API 中隨處可見,表示資料序列。例如,按鈕點選等 HTML 事件是使用流傳遞的。您也可以將檔案作為流讀取。
使用非同步 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 語言功能的更多資訊,請參閱非同步程式設計教程。
監聽流資料
#要獲取每個到達的值,請使用 await for 或使用 listen() 方法訂閱流
// 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() 等方法。
轉換流資料
#通常,在使用流資料之前,您需要更改其格式。使用 transform() 方法生成具有不同型別資料的流
var lines = inputStream
.transform(utf8.decoder)
.transform(const LineSplitter());此示例使用兩個轉換器。首先它使用 utf8.decoder 將整數流轉換為字串流。然後它使用 LineSplitter 將字串流轉換為單獨的行流。這些轉換器來自 dart:convert 庫(請參閱dart:convert 部分)。
處理錯誤和完成
#如何指定錯誤和完成處理程式碼取決於您是使用非同步 for 迴圈(await for)還是 Stream API。
如果您使用非同步 for 迴圈,則使用 try-catch 來處理錯誤。在流關閉後執行的程式碼位於非同步 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 監聽器來處理錯誤。透過註冊 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 文件。另請參閱以下文章和教程