Dart 异步编程与生成器详解:Future、Stream 及 typedef 用法
Dart 提供了强大的异步处理能力,通过 Future 处理单次异步操作结果,利用 Stream 接收连续事件流。掌握 async/await、yiled 生成器及 typedef 类型别名,可显著提升代码的可读性与维护性。
异步支持
Dart 标准库中包含大量返回 Future 或 Stream 对象的函数。这类被称为异步函数的操作通常涉及 IO 等耗时任务,它们不会阻塞当前线程等待完成,而是在任务完成后通过回调通知程序。
Future 基础概念
Future 类似于 JavaScript 中的 Promise,代表一个异步操作的最终完成(成功)或失败。其核心特征是每个 Future 对象只对应一个结果状态。所有 Future 的 API 返回值本身仍是 Future 对象,因此支持便捷的链式调用。
Future 常用方法
在实际开发中,常使用 Future.delayed 模拟耗时操作(如网络请求)。以下是 then、catchError 和 whenComplete 的具体用法:
第1步:使用 then 获取成功结果
then 用于在异步任务成功后执行回调。以下示例模拟一个 2 秒后返回 "hi world!" 的任务:
Future.delayed(Duration(seconds: 2), () {
return "hi world!";
}).then((data) {
print(data);
});
第2步:使用 catchError 或 onError 捕获异常
若异步过程抛出错误,可通过 catchError 独立捕获,或在 then 中添加 onError 参数进行内联处理:
// 方式一:独立 catchError
Future.delayed(Duration(seconds: 2), () {
throw AssertionError("Error");
}).then((data) {
print("success");
}).catchError((e) {
print(e);
});
// 方式二:使用 then 的 onError 参数
Future.delayed(Duration(seconds: 2), () {
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
第3步:使用 whenComplete 执行收尾工作
当无论成功或失败都需要执行清理逻辑(如关闭加载弹窗)时,应使用 whenComplete。它会在前序操作结束后无差别触发:
Future.delayed(Duration(seconds: 2), () {
throw AssertionError("Error");
}).then((data) {
print(data);
}).catchError((e) {
print(e);
}).whenComplete(() {
// 无论成功或失败都会走到这里
});
第4步:使用 wait 并发处理多个任务
Future.wait 接受一个 Future 列表。只有当列表中所有 Future 都成功完成时,才会触发成功回调;只要有一个失败,即触发整体错误回调。这与 JavaScript 的 Promise.all() 逻辑一致。
以下示例模拟两个不同耗时的异步任务,拼接结果:
Future.wait([
Future.delayed(Duration(seconds: 2), () {
return "hello";
}),
Future.delayed(Duration(seconds: 4), () {
return " world";
})
]).then((results) {
print(results[0] + results[1]);
}).catchError((e) {
print(e);
});
// 注:因第二个任务耗时 4 秒,总耗时取决于最慢的一个任务
async 和 await 关键字
虽然链式调用直观,但深层嵌套会影响可读性。引入 async 和 await 可使异步代码呈现同步风格。
async:修饰函数,表示该函数包含异步操作,自动将函数返回值包装为Future。await:暂停执行直到后续Future完成并返回结果,必须位于async函数内部。
对比同步与异步函数的定义:
// 普通同步函数
String lookUpVersion() => '1.0.0';
// 异步函数(返回值强制转为 Future<String>)
Future<String> lookUpVersion() async => '1.0.0';
推荐使用 try-catch 结构包裹 await 以优雅处理异常:
task() async {
try {
String id = await login("alice", "******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
// 继续后续逻辑
} catch (e) {
// 统一错误处理
print(e);
}
}
处理流 (Stream)
Stream 用于处理连续的数据事件流,适用于文件读写、网络下载或 UI 事件等场景。相比仅返回单一结果的 Future,Stream 可以触发多次成功或失败事件。
使用 await for 遍历流
处理 Stream 主要采用 await for 循环。注意:await for 会阻塞直至流关闭,且要求外层作用域标记为 async。在非无限流场景下使用能有效提升代码清晰度。
语法结构与流程如下:
await for (varOrType identifier in expression) {
// stream 每 emit 一次值,此处即执行一次
}
若要提前终止监听,可在循环体内使用 break 或 return 取消订阅。
使用 Stream API 监听数据
通过 listen 方法可以更灵活地处理流的生命周期事件:
data:接收到正常数据时触发。onError:发生异常时触发。onDone:流正常关闭时触发。
以下为结合 Future.delayed 构造多阶段流的示例:
Stream.fromFutures([
Future.delayed(Duration(seconds: 1), () {
return "hello 1";
}),
Future.delayed(Duration(seconds: 2), () {
throw AssertionError("Error");
}),
Future.delayed(Duration(seconds: 3), () {
return "hello 3";
})
]).listen(
(data) {
print(data);
},
onError: (e) {
print(e.message);
},
onDone: () {
// 流结束
}
); /*
输出顺序:
hello 1
Error
hello 3
*/
生成器
生成器函数支持按需延迟生成值序列。Dart 提供两种模式:
- 同步生成器:返回
Iterable,函数体标记为sync*。 - 异步生成器:返回
Stream,函数体标记为async*。
两者均使用 yield 语句逐个产出值。对于递归生成的场景,可使用 yield* 提高性能:
// 同步生成器示例
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
// 递归优化示例
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
类型别名 (typedef)
Dart 中函数是一等公民。typedef 可用于为特定函数签名创建类型别名,从而保留完整的类型信息,便于静态分析和重构工具识别。
未使用 typedef 时,作为字段传递的函数往往丢失具体的参数与返回类型约束,仅被识别为泛型 Function。使用 typedef 后可强化类型安全:
// 定义函数类型别名
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
void main() {
int sort(Object a, Object b) => 0;
SortedCollection coll = SortedCollection(sort);
// 此时断言校验生效,因为 retain 了具体签名
assert(coll.compare is Compare);
}
此外,typedef 也支持泛型,例如:
typedef Compare<T> = int Function(T a, T b);
assert(sort is Compare<int>); // True!
元数据
元数据用于向编译器或运行时提供关于代码的附加说明,由 @ 开头标识。Dart 内置了两个常用元数据注解:
@deprecated:标记过时的 API,并在 IDE 提示用户迁移至新接口。@override:显式声明该方法重写了父类成员,防止拼写错误导致的意外覆盖。
class Television {
@deprecated
void activate() {
turnOn();
}
void turnOn() { ... }
}
开发者也可自定义元数据类,通过构造函数传参,实现类似装饰器的效果:
library todo;
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
import 'todo.dart';
@Todo('dev', 'implement logic here')
void doSomething() {
print('doing...');
}
元数据可应用于库、类、字段、函数等声明处,并支持在运行时通过反射机制获取。
以上就是 Dart 异步编程、生成器及自定义类型用法的详细内容,更多关于 Dart 核心库开发的资料请关注本站其它相关文章!免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









