为什么 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
*/
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: 出错了
完成
*/
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com