为什么 Dart 要有异步
Dart 在 Flutter、服务端、命令行工具中都是单线程运行的(UI 线程 / 主线程),
如果某个任务很耗时(比如网络请求、文件读取、延迟操作),同步执行就会阻塞 UI 或主线程。
同步(Sync):
任务A执行完 → 再执行任务B → 再执行任务C
异步(Async):
任务A执行中 → 可以去做任务B
等A完成时通知你
Future —— Dart 的异步核心
Future 表示一个未来才会有结果的对象,结果可能是:
- 成功(返回数据)
- 失败(抛出异常)
一个入门案例
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 2), () {
return "Hello from Future";
});
}
void main() {
print("开始");
fetchData()
.then((data) {
print(data); // 2秒后打印 "Hello from Future"
})
.catchError((e) {
print("出错: $e");
});
print("结束");
}
解释:
Future.delayed模拟网络/IO延迟.then()处理成功结果.catchError()处理错误
返回值Future
简单定义(一句话): Future<T> 是一个占位对象,表示“将来某一刻会有一个类型为 T 的值(或发生错误)”。它不是值本身,而是值的承诺(placeholder)。
三种状态(概念)
- 未完成(pending):还没有结果,
Future在等着。 - 完成并带值(completed with value):将来某个时刻它会变成一个具体的
T值。 - 完成并带错误(completed with error):将来某个时刻它会变成一个错误/异常,而不是一个值。
泛型 T 的意义
Future<int>表示未来会有一个int值。Future<String>表示未来会有一个String。Future<void>表示未来没有返回值(只是完成或失败)。
跟直接返回值的区别(要点)
int foo():函数直接返回一个int—— 调用时你马上得到int。Future<int> foo():函数返回一个Future对象 —— 调用时你马上得到一个 Future,但不是int,这个int是“将来”才会在这个 Future 里出现。
// 直接返回值(同步)
int syncGet() {
return 1; // 调用者立即得到 1
}
// 返回 Future(表示未来会得到一个 int)
Future<int> futureGet() {
return Future.value(1); // 这里创建了一个已经完成的 Future,包含值 1
}
void main() {
var a = syncGet(); // a 是 int,值为 1
print("类型 a: ${a.runtimeType}, 值: $a"); // 输出: 类型 a: int, 值: 1
var b = futureGet(); // b 是 Future<int>,不是 int(它“承诺”将来包含 1)
print(
"类型 b: ${b.runtimeType}, 值: $b"
); // 输出: 类型 b: Future<int>, 值: Instance of 'Future<int>'
}
如果获取Future里面的值
在上面的一个案例中,我们直接打印的$b是Instance of 'Future<int>',而不是int里面的值,那么要如何才可以获取Future里面包含的值呢?
用 .then() 处理完成后的值
void main() {
var b = futureGet(); // 1️⃣ 创建 Future(未完成)
b.then((value) { // 2️⃣ 等完成后,回调获取值
print("b 中的值是 $value"); // 3️⃣ 这里的 value = 1
});
}
.then()会在 Future 完成时被调用,并把结果传给回调函数的参数value。回调里的代码会在将来(Future 完成时)执行。
在 async 函数中用 await 获取值
void main() async {
var b = futureGet(); // 1️⃣ 创建 Future(未完成)
var value = await b; // 2️⃣ 等完成,取出值
print("b 中的值是 $value"); // 3️⃣ 这里的 value = 1
}
await会暂停当前函数,直到 Future 完成,然后把结果赋给变量。- 这个方法必须放在
async函数中才能用。
时间轴示意(调用 futureGet() 获取 b,并取值)
时间 →
┌─────────────────────────────────────────────────────────────┐
│ main() 调用 futureGet() │
└─────────────────────────────────────────────────────────────┘
│
▼
b = Future<int> (状态:未完成,pending)
│
│ 等待异步任务执行中(此时 b 里没有 int 值)
│
▼
┌──────────────────────────────────────────────────────────┐
│ Future 完成(completed with value) │
│ b 内部存储了 int 值 1 │
└──────────────────────────────────────────────────────────┘
│
▼
then(...) 或 await b 触发回调 / 返回值
│
▼
value = 1 (现在才能拿到这个 int)
async 和await
作用
async 的作用是:
- 让一个普通函数自动返回 Future(即使你写的是普通返回值,它会包装成
Future)。 - 允许在函数体内使用
await。
await 的作用是:
让当前
async函数暂停,等Future完成后拿到结果。写起来像同步代码,避免链式
.then()。会让当前这个函数在这里暂停执行,直到
Future完成并返回结果,然后才继续向后执行。Future<void> main() async { print('A'); // 异步操作,模拟耗时任务 // await 会暂停执行,直到 Future 完成 await Future.delayed(Duration(seconds: 2), () => print('B')); print('C'); } /* A (等 2 秒) B C */
什么时候要 async
必须加 async 的场景
- 你在函数体里用了
await。 - 你想直接返回普通值,但希望函数的签名是
Future<T>,并且由async帮你包装成Future。
Future<String> foo() async { // 必须 async,因为用了 await
await Future.delayed(Duration(seconds: 1));
return "done";
}
什么时候不用 async
如果你的函数已经直接返回一个 Future 对象,而且里面不需要 await,就不必写 async。
Future<String> bar() {
// 直接返回 Future,不用 async
return Future.delayed(Duration(seconds: 1), () => "done");
}
这里如果写成 async 也能运行,但会多包一层微任务,性能略低(虽然一般无感知)。
什么时候要 await
必须加 await 的场景
- 你需要拿到
Future的结果值,再继续后续操作。 - 你想让当前函数等这个 Future 完成再返回。
- 启动多个Future任务,并且需要获得结果
// 加 await:要等 Future 完成后,拿到结果再做处理
Future<int> doubleNumber() async {
int n = await getNumber();
return n * 2;
}
// 启动多个Future任务,并且期待获得结果
Future<void> main() async {
print('开始执行');
// 三个异步任务
Future<int> task1 = Future.delayed(Duration(seconds: 3), () {
print('任务1完成');
return 1;
});
Future<int> task2 = Future.delayed(Duration(seconds: 2), () {
print('任务2完成');
return 2;
});
Future<int> task3 = Future.delayed(Duration(seconds: 1), () {
print('任务3完成');
return 3;
});
// 并行执行并等待全部完成
List<int> results = await Future.wait([task1, task2, task3]);
print('全部任务完成,结果: $results');
print('结束执行');
}
/*
开始执行
任务3完成
任务2完成
任务1完成
全部任务完成,结果: [1, 2, 3]
结束执行
*/
什么时候不用 await
不需要加 await 的场景
- 你只是要返回这个 Future,不在当前函数处理它的结果。
- 你要并行执行多个 Future,但是不需要获得结果
// 不加 await:直接返回 Future,让调用方处理
Future<int> getNumber() {
return Future.value(42);
}
void main() {
print('开始执行');
Future<int> task1 = Future.delayed(Duration(seconds: 3), () {
print('任务1完成');
return 1;
});
Future<int> task2 = Future.delayed(Duration(seconds: 2), () {
print('任务2完成');
return 2;
});
Future<int> task3 = Future.delayed(Duration(seconds: 1), () {
print('任务3完成');
return 3;
});
// 启动并行任务,但不等结果
Future.wait([task1, task2, task3]);
print('我已经往下执行了,不等任务完成,也不需要结果');
}
/*
开始执行
我已经往下执行了,不等任务完成,也不需要结果
任务3完成
任务2完成
任务1完成
*/
链式写法
链式写法是怎么工作的
Future 本身就有方法 .then()、.catchError()、.whenComplete()
.then()` 接收一个回调,回调的返回值(无论是普通值还是 `Future`)都会传给下一个 `.then()
如果中间某个 .then() 抛出了异常,后续的 .then() 会跳过,直接进入最近的 .catchError()
.whenComplete() 不管成功还是失败都会执行(类似 try...finally 里的 finally)
示例
void main() {
print('开始执行 Future 链');
Future(() => 1) // 创建 Future,返回 1
.then((v) {
print('第一个 then: $v');
return v + 1; // 变成 2
})
.then((v) {
print('第二个 then: $v');
// 返回一个延迟 Future
return Future.delayed(Duration(seconds: 2), () => v * 2);
})
.then((v) {
print('第三个 then: $v'); // 结果是 4
})
.catchError((e) {
print('捕获错误: $e');
})
.whenComplete(() {
print('链结束了');
});
print('继续执行其他代码');
// 也可以并行多个 Future
Future.wait([
Future.delayed(Duration(milliseconds: 30), () => "A"),
Future.delayed(Duration(milliseconds: 10), () => "B"),
]).then((list) => print("wait results: $list"));
print('结束执行 Future 链');
}
/*
开始执行 Future 链
继续执行其他代码
结束执行 Future 链
第一个 then: 1
第二个 then: 2
wait results: [A, B]
第三个 then: 4
链结束了
*/
链式写法与 async/await 的比较
比较两种写法
| 对比点 | 链式写法 (then / catchError) |
async/await |
|---|---|---|
| 可读性 | 代码可能向右缩进(回调地狱),可读性较差 | 更接近同步写法,直观易读 |
| 错误处理 | 用 .catchError() 捕获异常 |
用 try...catch 捕获异常 |
| 返回值传递 | 上一个 .then() 的返回值会自动传给下一个 .then() |
await 会直接得到上一个 Future 的结果 |
| 调试体验 | 调试堆栈可能更分散,不连续 | 更容易逐行调试 |
| 组合多个任务 | 通过 Future.wait、Future.forEach 等 |
也能配合 Future.wait 等 API |
| 灵活性 | 在流式数据处理、事件驱动、函数式链式调用中更自然 | 在需要等待顺序执行的业务逻辑中更自然 |
等价写法
Future(() => 1)
.then((v) => v + 1)
.then((v) => Future.delayed(Duration(milliseconds: 50), () => v * 2))
.then((v) => print("final: $v"))
.catchError((e) => print("error: $e"))
.whenComplete(() => print("done"));
Future<void> main() async {
try {
var v = await Future(() => 1);
v = v + 1;
v = await Future.delayed(Duration(milliseconds: 50), () => v * 2);
print("final: $v");
} catch (e) {
print("error: $e");
} finally {
print("done");
}
}
功能等价,但 async/await 更像同步代码,而链式写法更函数式、适合处理流(Stream)或复杂的条件分支。
一个误区
链式写法(then)本身并不会阻塞任何代码执行——但其实 async/await 也同样不会阻塞整个主方法,它只是让所在的异步函数在 await 那一行“暂停”直到 Future 完成,而不是卡住整个程序的事件循环。
void main() {
print('start');
// async/await 写法
runTask();
// 链式写法
Future.delayed(Duration(seconds: 1)).then((_) => print('链式任务完成'));
print('end');
}
Future<void> runTask() async {
await Future.delayed(Duration(seconds: 1));
print('async/await 任务完成');
}
/*
start
end
async/await 任务完成
链式任务完成
*/
Future 的其他特性
Future 构造器
Future 构造器:Future(() => ...):把执行体放到 事件队列(event queue) 中异步执行,适用于把一个立即计算的任务推到下一轮事件循环执行。
void main() {
print("sync start");
Future(() {
// 这个函数将在事件队列中异步执行
print("in Future constructor");
return 42;
}).then((v) => print("value: $v"));
print("sync end");
}
/*
sync start
sync end
in Future constructor
value: 42
*/
Future.value() / Future.error()
Future.value() / Future.error():快速创建一个已经完成的 Future(常用于测试或需要返回立即已知结果的 API)。
// Future.value(100):创建一个立即完成的 Future,值为 100。
// Future.error(Exception("oops")):创建一个立即失败的 Future,抛出异常
// 注意:这两个 Future 都是立即完成或失败的,不需要等待。
// 这在测试或模拟异步操作时非常有用。
Future<int> immediate() => Future.value(100);
Future<int> failed() => Future.error(Exception("oops"));
void main() async {
print(await immediate()); // 100
try {
await failed();
} catch (e) {
print("caught: $e"); // caught: Exception: oops
}
}
Future.delayed():延迟(模拟等待/定时)
Future<String> delayedHello() {
// Future.delayed:创建一个在未来某个时间点完成的 Future
// Duration(seconds: 2):表示延迟 2 秒
// 返回一个字符串 "hello after 2s"
// 注意:这个 Future 在 2 秒后才会完成
return Future.delayed(Duration(seconds: 2), () => "hello after 2s");
}
void main() async {
// 直接调用 delayedHello(),它会返回一个 Future<String>
// 使用 await 等待 Future 完成,并获取结果
print(await delayedHello());
}
Future.microtask() 和 scheduleMicrotask()
Future.microtask() 和 scheduleMicrotask():微任务队列(优先级高)
微任务会在当前同步执行完成后、事件队列任务之前运行。
- 微任务队列(microtasks)优于事件队列(events)。
Future.microtask()和scheduleMicrotask()的优先级完全相同
用于需要尽快但仍异步执行的小任务。
import 'dart:async';
void main() {
print("start");
Future(() => print("event future"));
scheduleMicrotask(() => print("microtask 1"));
Future.microtask(() => print("microtask 2"));
print("end");
}
/*
start
end
microtask 1
microtask 2
event future
*/
Future.doWhile
在 Dart 里,Future.doWhile 是一个异步循环,定义大概是这样:
Future doWhile(FutureOr<bool> Function() action)
action:一个函数(返回bool或Future<bool>)。- 返回值:当
action返回false时,循环结束,整个Future完成。
它和普通的 while 类似,但区别在于:
- 普通
while是同步循环(会阻塞线程)。 Future.doWhile是异步循环(不会阻塞 UI),每次执行完action后,再决定是否继续。
流程是这样的:
- 执行
action()。 - 如果
action返回true,再执行一次。 - 如果
action返回false,循环停止。
import 'dart:async';
void main() async {
int count = 0;
await Future.doWhile(() async {
print("循环次数: $count");
await Future.delayed(Duration(seconds: 1)); // 模拟异步任务
count++;
return count < 3; // 只有小于3时才继续循环
});
print("循环结束");
}
/*
循环次数: 0
循环次数: 1
循环次数: 2
循环结束
*/
Stream
Stream是什么
在 Dart 里,Stream 就是一个异步事件的数据管道,它可以多次产出数据,而不是像 Future 那样一次性返回结果。
可以用一个比喻来理解:
Future就像点外卖——你下单一次,等到饭送来,结果就结束了。Stream就像水龙头——你一旦打开,它会不断“流出”事件/数据,直到被关闭。
Stream 的特性:
- 异步:数据不是立刻到达,而是随着时间推送。
- 多次产出数据:可以发出一个、两个……甚至无限个事件。
- 监听模式:你订阅(
listen)它,它就会按顺序发数据给你。 - 支持错误事件:可以发出错误信号,而不是只抛异常。
- 有结束信号:告诉监听者“流”已经结束。
创建 Stream
// async*: 用于定义异步生成器函数,返回一个 Stream。
Stream<int> countStream() async* {
for (int i = 1; i <= 3; i++) {
// 模拟异步操作
await Future.delayed(Duration(seconds: 1)); // 模拟耗时
// 使用 yield 发送数据到 Stream
yield i; // 发送一个数据,发给 Stream 的监听者
}
}
async*:异步生成器,表示这个函数返回的是 Stream。
yield:发送一个事件(一个数据)。
await:依旧可以用来等待异步操作。
监听 Stream
方式 1:await for
void main() async {
// countStream(): 返回一个 Stream<int>,可以异步获取数据
// 使用 await for 循环来异步接收 Stream 中的数据
print("开始接收数据");
await for (var number in countStream()) {
print(number);
}
print("数据接收完毕");
}
/*
开始接收数据
1
2
3
数据接收完毕
*/
await for会顺序等待 每个事件。- 类似同步
for循环,但每次循环会等到下一次事件到达。
方式 2:listen
void main() {
print("开始接收数据");
// 监听 countStream() 返回的 Stream<int>
// 使用 listen 方法来处理 Stream 中的数据
countStream().listen((number) {
print('收到: $number');
});
print("数据接收完毕");
}
/*
开始接收数据
数据接收完毕
收到: 1
收到: 2
收到: 3
*/
listen 会返回一个 StreamSubscription 对象,可以随时暂停/恢复/取消订阅。
Stream 里的事件类型
Dart 的 Stream 不只会发“数据事件”,还有:
- 数据事件(data event) →
yield或者add产生的。 - 错误事件(error event) → 用
addError或throw触发。 - 完成事件(done event) → 表示 Stream 结束,不会再有数据。
Stream<int> errorDemo() async* {
yield 1;
throw Exception('出错了'); // 抛出异常
// 下面的 yield 不会被执行,因为上面的异常会终止 Stream
yield 2; // 不会执行
}
void main() {
errorDemo().listen(
(data) => print('数据: $data'), // 处理数据
onError: (err) => print('错误: $err'), // 处理错误
onDone: () => print('完成'), // 处理完成事件,也就是 Stream 结束时调用
);
}
/*
数据: 1
错误: Exception: 出错了
完成
*/