文章目录

Dart 异步编程:Future 与 async/await

发布于 2026-04-06 22:45:20 · 浏览 9 次 · 评论 0 条

Dart 异步编程:Future 与 async/await

Dart 采用单线程运行机制,所有代码在同一时间只能执行一项任务。遇到网络请求、文件读写等耗时操作时,若直接原地等待,会导致主线程阻塞、界面卡顿。引入异步编程,能让主线程在等待耗时操作完成期间,继续处理其他逻辑,从而保证程序流畅运行。 本指南将手把手教你使用 Futureasync/await 高效编写 Dart 异步代码。


阶段一:理解异步执行的基本流程

在编写代码前,明确 Dart 处理异步任务的标准路径。异步任务不会立即返回结果,而是返回一个“占位符”对象,待后台任务完成后,再将真实数据填充进去。

  1. 创建 Future 实例。该实例代表一个将在未来某个时刻完成的操作。
  2. 注册 回调函数。通过指定成功或失败时的处理逻辑,确保数据返回后程序能正确响应。
  3. 监听 状态变化。Dart 事件循环会在后台任务结束后,自动触发对应的回调逻辑。
异步对象类型 适用场景说明 返回值特征
Future<T> 任务有确定返回值 返回具体泛型数据类型 T
Future<void> 仅需确认任务完成状态 无实际返回值
Future.error 明确标识异步任务将失败 直接抛出异常对象

阶段二:编写并处理原生 Future

使用 Dart 原生提供的工具函数生成 Future,并学会使用 .then() 链式调用获取结果。

  1. 导入 异步操作基础库。在文件顶部添加 import 'dart:async'; 确保基础类型可用(现代 Dart 版本已内置,但显式声明可增强代码可读性)。
  2. 包装 耗时逻辑。使用 Future.delayed 函数模拟需要等待的操作。该函数接收两个参数:第一个是 Duration 类型的时间间隔,第二个是闭包函数(即具体要执行的任务)。
  3. 链接 处理逻辑。在 Future 对象后调用 .then() 方法,传入接收返回值的函数。
  4. 打印 验证结果。在终端运行代码,观察 控制台输出顺序,确认耗时操作未阻塞后续同步代码的执行。
// 模拟耗时操作:2秒后返回字符串结果
void main() {
  print('任务开始');

  Future.delayed(Duration(seconds: 2), () {
    return '网络请求成功:数据已获取';
  }).then((result) {
    print('回调执行:$result');
  });

  print('主线程继续运行');
}
// 预期输出顺序:任务开始 -> 主线程继续运行 -> (等待2秒) -> 回调执行:网络请求成功:数据已获取
```

5. **捕获** 链式异常。在 `.then()` 后追加 `.catchError()` 方法,传入错误处理函数,防止未处理的异常导致程序崩溃。

---

### 阶段三:使用 async/await 重构代码
`.then()` 链式调用在逻辑复杂时会形成多层嵌套。使用 `async/await` 语法糖,可将异步代码写得像普通同步代码一样直观。

1. **标记** 异步函数。在函数返回类型前添加 `Future` 关键字,并在函数名与参数列表之间添加 `async` 关键字。此操作告知 Dart 编译器该函数内部包含异步逻辑。
2. **阻塞等待** 结果。在可能耗时的 `Future` 表达式前添加 `await` 关键字。程序执行到此处时会暂停该函数内部的后续代码,直到 `Future` 完成并返回结果,再将结果赋值给左侧变量。
3. **包裹** 异常逻辑。将包含 `await` 的代码块放入标准的 `try { ... } catch (e) { ... }` 结构中,直接使用 `catch` 捕获同步风格的异常。

```dart
Future<String> fetchNetworkData() async {
  try {
    // 等待模拟网络请求,耗时1秒
    String data = await Future.delayed(Duration(seconds: 1), () => '用户信息');
    print('数据加载完成:$data');
    return data;
  } catch (e) {
    print('请求失败:$e');
    throw Exception('数据获取异常');
  }
}

Future<void> processData() async {
  print('开始处理');
  // 等待 fetchNetworkData 完成并获取结果,代码在此暂停
  String result = await fetchNetworkData();
  // 只有上方 await 完成后,才会执行下方代码
  print('处理完毕,使用结果:$result');
}
  1. 验证 执行流。运行 processData()确认 print('开始处理') 执行后,程序等待 1 秒,随后打印加载完成信息,最后打印处理完毕信息。整个流程符合自上而下的阅读直觉。

阶段四:配置超时与降级机制

网络请求或外部服务可能永久挂起。设置 明确的超时阈值,防止 await 无限期阻塞程序。

  1. 引入 超时控制。直接在目标 Future 表达式后调用 .timeout() 方法。
  2. 定义 时间阈值。向 .timeout() 传入 Duration 对象作为第一个参数,设定允许等待的最大时间。
  3. 指定 超时行为。通过 onTimeout 参数传入一个回调函数。该函数必须返回一个与原 Future 类型一致的值,或抛出特定异常。
import 'dart:async';

Future<String> fetchWithTimeout() async {
  try {
    // 设置最大等待时间为 500 毫秒
    String res = await Future.delayed(Duration(seconds: 2)).timeout(
      Duration(milliseconds: 500),
      onTimeout: () {
        throw TimeoutException('接口响应超时');
      },
    );
    return res;
  } catch (e) {
    return '降级数据:使用本地缓存';
  }
}
  1. 调整 降级策略。根据业务需求修改 onTimeout 的返回值。若业务强依赖实时数据,抛出 异常由上层统一处理;若允许数据延迟,返回 静态兜底数据保证界面正常渲染。

阶段五:并行执行独立异步任务

多个相互独立的异步操作若使用多个 await 串行执行,总耗时会线性累加。改用 并行执行策略,显著缩短整体等待时间。

  1. 收集 任务列表。将所有独立的异步 Future 对象存入一个 List 集合中,确保它们处于已触发但未等待的状态。
  2. 调用 等待方法。使用 Future.wait() 静态方法包裹任务列表。该方法会返回一个新的 Future,当列表中所有任务全部完成时,该新 Future 才会结束。
  3. 提取 组合结果。Future.wait() 返回的结果是一个列表,顺序与传入任务的顺序严格一致。使用索引或解构语法快速读取各任务返回值。
Future<void> runParallelTasks() async {
  // 1. 立即触发所有任务(注意:此处声明时不加 await)
  Future<String> task1 = Future.delayed(Duration(seconds: 2), () => '数据库连接');
  Future<String> task2 = Future.delayed(Duration(seconds: 1), () => '配置文件加载');
  Future<String> task3 = Future.delayed(Duration(seconds: 1), () => '日志初始化');

  // 2. 统一等待,总耗时取决于最慢的任务(约2秒,而非4秒)
  List<String> results = await Future.wait([task1, task2, task3]);

  print('所有任务完成');
  print(results[0]); // 输出:数据库连接
  print(results[1]); // 输出:配置文件加载
  print(results[2]); // 输出:日志初始化
}
  1. 处理 单点失败。若需允许部分任务失败而不中断整体流程,在 Future.wait() 中设置 eagerError: false 参数。使用 try-catch 捕获错误,遍历结果列表时判断 每一项的状态,将失败项替换为默认值。

  2. 对比 性能差异。分别运行串行(多次独立 await)与并行(Future.wait)代码,记录 控制台总耗时差值。并行方案将多个任务的耗时从相加变为取最大值,CPU 时间片利用率大幅提升。

  3. 应用 组合策略。在实际业务中,将核心流程拆分为无依赖并行阶段与有依赖串行阶段。先使用 Future.wait 批量加载基础资源,再利用 await 按顺序处理资源关联逻辑。

  4. 封装 通用工具类。将常用的异步模板抽取为独立函数,统一在返回值前添加 async,在调用处统一使用 await 获取结果。确保每个异步函数只负责单一职责,返回 明确的 Future<T> 类型。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文