异步编程 通常会使用回调函数,但 Dart 提供了另一种替代方案:Future 和 Stream 对象。
Future:类似于一个 “承诺”,表示未来某个时刻会返回一个结果。Stream:用于获取一系列连续的值(例如事件流)。
Future、Stream 等核心异步类型都位于 dart:async 库中。
1. Future
一个 Future 对象表示一个计算任务,其返回值当前可能尚不可用,会在未来某个时刻完成(complete) 时返回计算结果。
Future 常用于需要在其他线程 / Isolate 中执行的 API,例如:
dart:io的异步I/O操作dart:html的HTTP请求
1.1 基本用法
Dart 库中很多方法执行任务时都会返回 Future。例如,将 HttpServer 绑定到地址和端口时,bind() 方法会返回一个 Future。
HttpServer.bind('127.0.0.1', 4444)
.then((server) => print('${server.isBroadcast}'))
.catchError(print);Future.then:注册一个回调,当Future操作成功完成时执行,操作的返回值会传入回调。本例中bind()返回HttpServer对象;Future.catchError:注册一个回调,当Future内部发生错误时执行。
1.2 await 和 async
在使用 Future 时,往往应该优先考虑 await,因为使用 await 的代码通常比直接链式调用 then() 更简洁易读。
例如,下述代码:
void runUsingFuture() {
// ...
findEntryPoint()
.then((entryPoint) {
return runExecutable(entryPoint, args);
})
.then(flushThenExit);
}等价于:
Future<void> runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}可以直接用 try-catch 捕获异常,等价于基础用法中的 catchError():
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// 处理异常……
}async 函数一定会返回 Future,如果不希望函数返回 Future,就不要用 async,可选择在普通函数里调用 async 函数。
1.3 等待多个 Future
有时需要并行执行多个异步任务,并全部完成后再继续。这时可以使用静态方法 Future.wait() 来管理多个 Future:
Future<void> deleteLotsOfFiles() async => ...
Future<void> copyLotsOfFiles() async => ...
Future<void> checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('所有长耗时步骤已完成!');Future.wait() 会在所有 Future 完成后才完成,如果成功,则返回所有结果列表,如果任意一个失败,则整体抛出错误。
1.4 多 Future 错误处理
扩展方法支持对列表 / 记录中的多个 Future 进行等待,并分别捕获错误。这些扩展返回包含所有结果的 Future,并且支持错误隔离,不像 Future.wait() 一错全错。
1.4.1 不需要单独结果值(列表)
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
void main() async {
try {
var results = await [delete(), copy(), errorResult()].wait;
} on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {
print(e.values[0]); // 成功则返回结果
print(e.values[1]); // 成功则返回结果
print(e.values[2]); // 失败则返回 null
print(e.errors[0]); // 成功则返回 null
print(e.errors[1]); // 成功则返回 null
print(e.errors[2]); // 失败则返回错误
}
}1.4.2 需要单独结果值(记录)
Future<int> delete() async => ...;
Future<String> copy() async => ...;
Future<bool> errorResult() async => ...;
void main() async {
try {
final (deleteInt, copyString, errorBool) =
await (delete(), copy(), errorResult()).wait;
} on ParallelWaitError<
(int?, String?, bool?),
(AsyncError?, AsyncError?, AsyncError?)
> catch (e) {
// ...
}
}2. Stream
一个 Stream 提供异步的数据序列。典型的数据序列包括:
- 单次事件(如鼠标点击)
- 大块数据的连续分片(如文件读取的字节流)
2.1 基本用法(listen)
下面是一个用 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), searchPath);
}
});
}2.1 await for
可以用 await for 遍历 Stream,上述代码等价于:
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 for 是一个异步循环,它会一直暂停当前函数的执行,直到 Stream 关闭(Done)。通常不应该在 UI 事件监听器上使用 await for,因为 UI 框架发送的是源源不断的事件流,否则会阻塞后续代码,但依然可以使用 listen() 方法。await for 适用于那些有限且确实需要按顺序处理所有结果的 Stream(如读取文件行、WebSocket 接收直到连接关闭)。
2.2 转换 Stream 数据
常用 transform() 对数据格式进行转换:
var lines = inputStream
.transform(utf8.decoder)
.transform(const LineSplitter());2.3 错误与完成处理
2.3.1 使用 await for
用 try-catch 捕获错误,流关闭后代码继续执行:
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);
}
}2.3.2 使用 listen()
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);
},
); 相关推荐
- Dart 基础要点(4) —— 枚举 2026-05-14
- Dart 基础要点(3) —— 类和对象 2025-09-20
- Dart 基础要点(2) —— 函数 2025-09-15
- Dart 基础要点(1) —— 注释、变量、常量、数据类型 2025-09-14
- Dart 开发环境搭建 2025-09-12
评论0
暂时没有评论