在前面的学习中,我们写的代码几乎都是同步执行 的 ------ 代码从上到下依次运行,前一行执行完才会执行下一行。但在实际开发中,很多操作(如网络请求、文件读写)需要等待外部响应,若用同步方式处理会导致程序 "卡死"。今天我们要学习的异步编程,就是解决这类问题的核心技术,尤其在 UI 程序(如 Flutter 应用)中至关重要。
一、同步 vs 异步:为什么 UI 程序离不开异步?
1. 同步执行的局限性
同步代码的执行流程像一条直线,每一步必须等待上一步完成:
dart
void main() {
print("开始执行");
print("执行耗时操作...");
// 模拟一个耗时 3 秒的操作(如网络请求)
for (int i = 0; i < 1000000000; i++) {}
print("操作完成");
print("继续执行其他任务");
}
// 输出顺序:
// 开始执行
// 执行耗时操作...
// (等待 3 秒)
// 操作完成
// 继续执行其他任务
这种模式在耗时操作(如网络请求、大数据处理)面前会出现严重问题:
- 整个程序会被 "阻塞",无法响应用户操作(如点击按钮、滑动屏幕)
- UI 会冻结,给用户带来 "卡死" 的糟糕体验
2. 异步执行的优势
异步代码的核心是:耗时操作在 "后台" 执行,主线程继续处理其他任务,待耗时操作完成后再回调处理结果。
用生活类比:
- 同步:烧开水时一直盯着水壶,什么也不做,水开了再倒水泡茶。
- 异步:烧开水时去准备茶叶和杯子,水开了再回来倒水泡茶。
在 UI 程序中,异步是刚需:
- 保证 UI 线程不被阻塞,始终能响应用户操作
- 提升程序运行效率,多个任务可 "并行" 处理
二、Future:异步操作的 "占位符"
Dart 中用 Future
表示一个 "未来会完成的操作",它是异步操作的 "占位符"------ 在创建时我们不知道结果,但知道未来某个时刻会得到结果(成功或失败)。
1. Future 的三种状态
- 未完成(pending) :异步操作正在执行,结果尚未产生
- 已完成(completed with value) :异步操作成功,返回结果值
- 已完成(completed with error) :异步操作失败,返回错误信息
2. 创建 Future 与处理结果
通过 Future
构造函数创建异步操作,并用 then
处理成功结果,catchError
处理错误:
dart
void main() {
print("开始执行主线程任务");
// 创建一个 Future(异步操作)
Future<String> fetchData() {
// 模拟网络请求,2 秒后返回结果
return Future.delayed(Duration(seconds: 2), () {
// 模拟成功场景
return "获取到的数据:Dart 异步编程";
// 模拟失败场景(取消上面一行注释,启用下面一行)
// throw Exception("网络错误,获取数据失败");
});
}
// 调用异步函数,得到 Future 对象
Future<String> future = fetchData();
// 注册回调:当 Future 成功完成时执行
future
.then((data) {
print("异步操作成功:$data");
})
.catchError((error) {
// 注册回调:当 Future 失败时执行
print("异步操作失败:${error.toString()}");
})
.whenComplete(() {
// 注册回调:无论成功或失败,最终都会执行
print("异步操作结束(无论成败)");
});
print("主线程继续执行其他任务");
}
// 输出顺序(成功场景):
// 开始执行主线程任务
// 主线程继续执行其他任务
// (等待 2 秒)
// 异步操作成功:获取到的数据:Dart 异步编程
// 异步操作结束(无论成败)
关键特点:
- 调用
fetchData()
后,主线程不会等待,直接执行下一行print
- 2 秒后异步操作完成,才会执行
then
中的回调函数 catchError
捕获异步操作中抛出的错误whenComplete
类似 "finally",无论成败都会执行
3. Future.value 与 Future.error
快速创建 "已完成" 的 Future:
dart
void main() {
// 直接创建一个成功的 Future
Future.value("直接返回成功结果").then((data) {
print(data); // 输出:直接返回成功结果
});
// 直接创建一个失败的 Future
Future.error(Exception("直接返回错误")).catchError((error) {
print(error); // 输出:Exception: 直接返回错误
});
}
三、async/await:用同步风格写异步代码(重点)
虽然 then
链式调用可以处理异步,但多层嵌套时会导致 "回调地狱"(代码臃肿、难以维护)。Dart 提供了 async/await
语法糖,让我们能用同步代码的写法来编写异步逻辑。
1. 基本用法
async
:修饰函数,表明该函数是异步的,返回值会自动包装为Future
await
:只能在async
函数中使用,等待Future
完成并获取结果
dart
void main() {
print("开始执行主线程任务");
// 调用异步函数(无需等待,主线程继续执行)
fetchAndPrintData();
print("主线程继续执行其他任务");
}
// 用 async 修饰,表明这是异步函数
Future<void> fetchAndPrintData() async {
try {
print("开始异步获取数据");
// 用 await 等待 Future 完成,获取结果(类似同步代码的写法)
String data = await fetchData(); // 等待 fetchData() 完成
// 上面的 await 会"暂停"当前函数,直到 Future 完成
print("异步操作成功:$data");
} catch (error) {
// 捕获异步操作中的错误(替代 catchError)
print("异步操作失败:${error.toString()}");
} finally {
// 无论成功失败都会执行(替代 whenComplete)
print("异步操作结束(无论成败)");
}
}
// 模拟网络请求的异步函数
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 2), () {
return "获取到的数据:async/await 真方便";
// 模拟失败:throw Exception("网络错误");
});
}
// 输出顺序:
// 开始执行主线程任务
// 主线程继续执行其他任务
// 开始异步获取数据
// (等待 2 秒)
// 异步操作成功:获取到的数据:async/await 真方便
// 异步操作结束(无论成败)
2. 为什么 async
函数返回 Future
?
- 被
async
修饰的函数,其返回值会被自动包装为Future
- 若函数返回
int
,实际返回类型是Future<int>
- 若函数无返回值,实际返回类型是
Future<void>
dart
// 返回 Future<int>
Future<int> calculate() async {
await Future.delayed(Duration(seconds: 1));
return 100; // 自动包装为 Future.value(100)
}
void main() async {
int result = await calculate(); // 用 await 获取结果
print(result); // 输出:100
}
3. 处理多个异步操作
async/await
让多个异步操作的顺序执行变得简单:
dart
// 模拟三个异步操作
Future<String> fetchUser() =>
Future.delayed(Duration(seconds: 1), () => "用户信息");
Future<String> fetchOrders() =>
Future.delayed(Duration(seconds: 1), () => "订单列表");
Future<String> fetchRecommendations() =>
Future.delayed(Duration(seconds: 1), () => "推荐商品");
// 顺序执行多个异步操作
Future<void> fetchAllData() async {
print("开始获取所有数据...");
// 按顺序执行,总耗时 ~3 秒
String user = await fetchUser();
print("获取到:$user");
String orders = await fetchOrders();
print("获取到:$orders");
String recommendations = await fetchRecommendations();
print("获取到:$recommendations");
print("所有数据获取完成");
}
void main() {
fetchAllData();
}
若多个异步操作无依赖关系,可并行执行(用 Future.wait
):
dart
Future<void> fetchAllDataParallel() async {
print("开始并行获取所有数据...");
// 并行执行,总耗时 ~1 秒(取最长的单个操作时间)
List<Future<String>> futures = [
fetchUser(),
fetchOrders(),
fetchRecommendations(),
];
// 等待所有 Future 完成,返回结果列表
List<String> results = await Future.wait(futures);
for (String result in results) {
print("获取到:$result");
}
print("所有数据获取完成");
}
四、异步编程常见陷阱
1. 忘记使用 await
dart
Future<int> getNumber() async => 42;
void main() async {
// 错误:忘记用 await,得到的是 Future 而不是结果
var result = getNumber();
print(result); // 输出:Instance of 'Future<int>'
// 正确:用 await 获取结果
var correctResult = await getNumber();
print(correctResult); // 输出:42
}
2. 在非 async
函数中使用 await
dart
// 错误:await 只能在 async 函数中使用
void badFunction() {
// await Future.delayed(Duration(seconds: 1)); // 编译错误
}
// 正确:用 async 修饰函数
void goodFunction() async {
await Future.delayed(Duration(seconds: 1)); // 正确
}
3. 未处理异常导致程序崩溃
dart
Future<void> riskyOperation() async {
throw Exception("意外错误"); // 抛出异常
}
void main() async {
// 错误:未处理异常,会导致程序崩溃
// await riskyOperation();
// 正确:用 try-catch 处理异常
try {
await riskyOperation();
} catch (e) {
print("捕获到异常:$e"); // 安全处理
}
}
五、实际应用场景
异步编程在实际开发中无处不在:
网络请求:
dart
Future<User> fetchUserInfo(String userId) async {
final response = await http.get(
Uri.parse("https://api.example.com/users/$userId"),
);
if (response.statusCode == 200) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception("Failed to load user");
}
}
本地存储操作:
dart
Future<void> saveData(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, value);
}
延迟执行:
dart
Future<void> showSplashScreen() async {
print("显示启动页");
await Future.delayed(Duration(seconds: 3)); // 等待 3 秒
print("关闭启动页,进入首页");
}