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) {
// 处理错误...
}
有关使用 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) {
// 处理或忽略错误。
});
then().catchError()
模式是 try
- catch
的异步版本。
多个异步方法的链式调用
#then()
方法返回一个 Future,提供了一种以特定顺序运行多个异步函数的有用方法。如果使用 then()
注册的回调返回一个 Future, then()
将返回一个 Future,该 Future 将使用与回调返回的 Future 相同的结果完成。如果回调返回任何其他类型的值, then()
将创建一个完成该值的新 Future。
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* 处理异常... */
});
在前面的示例中,方法按以下顺序运行:
costlyQuery()
expensiveWork()
lengthyComputation()
以下是使用 await 编写的相同代码:
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* 处理异常... */
}
等待多个 futures
#有时你的算法需要调用许多异步函数,并在它们全部完成之前等待它们。使用 Future.wait() 静态方法来管理多个 Futures 并等待它们完成:
Future<void> deleteLotsOfFiles() async => ...
Future<void> copyLotsOfFiles() async => ...
Future<void> checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('所有漫长的步骤都完成了!');
Future.wait()
返回一个 future,一旦所有提供的 futures 完成,它就完成。它要么使用它们的结果完成,要么如果任何提供的 futures 失败则使用错误完成。
处理多个 futures 的错误
#你也可以在一个 iterable 或 record 的 futures 上等待并行操作。
这些扩展返回一个 future,其中包含所有提供的 futures 的结果值。与 Future.wait
不同的是,它们还允许你处理错误。
如果集合中的任何 future 以错误完成, wait
将以一个 ParallelWaitError
完成。这允许调用者处理单个错误,并在必要时处理成功的结果。
当你 不需要 每个单独 future 的结果值时,在一个 futures 的 iterable 上使用 wait
:
void main() async {
Future<void> delete() async => ...
Future<void> copy() async => ...
Future<bool> errorResult() async => ...
try {
// 等待列表中的每个 future,返回一个 futures 列表:
var results = await [delete(), copy(), errorResult()].wait;
} on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
print(e.values[0]); // 打印成功的 future
print(e.values[1]); // 打印成功的 future
print(e.values[2]); // 当结果是错误时打印 null
print(e.errors[0]); // 当结果成功时打印 null
print(e.errors[1]); // 当结果成功时打印 null
print(e.errors[2]); // 打印错误
}
}
当你 需要 每个 future 的各个结果值时,在一个 futures 的 record 上使用 wait
。这提供了额外的优点,即 futures 可以是不同类型的:
void main() async {
Future<int> delete() async => ...
Future<String> copy() async => ...
Future<bool> errorResult() async => ...
try {
// 等待记录中的每个 future,返回一个 futures 记录:
(int, String, bool) result = await (delete(), copy(), errorResult()).wait;
} on ParallelWaitError<(int?, String?, bool?),
(AsyncError?, AsyncError?, AsyncError?)> catch (e) {
// ...
}
// 对结果做一些事情:
var deleteInt = result.$1;
var copyString = result.$2;
var errorBool = result.$3;
}
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()
方法订阅流:
// 向按钮添加事件处理程序。
submitButton.onClick. listen((e) {
// 单击按钮时,它会运行此代码。
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('从流中获取了 ${line.length} 个字符');
}
print('文件现在已关闭');
} 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('从流中获取了 ${line.length} 个字符');
}, onDone: () {
print('文件现在已关闭');
}, onError: (e) {
print(e);
});
更多信息
#有关在命令行应用中使用 Future 和 Stream 的一些示例,请查看 dart:io 文档 。另请参阅以下文章和教程:
除非另有说明,否则本网站上的文档反映的是 Dart 3.6.0。页面最后更新于 2025-02-05。 查看源代码 或 报告问题.