Dart 语言的异步编程 Future 和 Stream

FreeGuideOnline 最新 2026-07-09

单线程模型与异步任务

Dart 是一门单线程语言,所有代码默认运行在一个独立的事件循环(event loop)上。如果某个操作需要等待较长时间(如读取文件、网络请求),同步执行会阻塞整个线程,导致界面卡死。异步编程正是为了解决这类问题而生:把耗时操作交给系统执行,当前线程继续处理其他任务,待操作完成后再通过回调、FutureStream 取回结果。

Dart 提供两种核心异步抽象:Future 表示一个即将完成(或失败)的单个值,Stream 则表示一串连续的异步事件或数据。

Future:代表未来的单个值

Future<T> 就像一个承诺:它现在可能没有值,但会在未来某个时刻提供类型为 T 的结果,或者抛出一个错误。任何返回 Future 的函数都会被立即挂起,将控制权交还事件循环,直到操作完成。

创建 Future

最直接的方式是使用 Future 构造函数:

Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () => '数据加载完成');
}

Future.delayed 模拟了一个延时操作。你也可以用 Future.value() 立即返回一个已知值,或用 Future.error() 立即返回一个错误。

消费 Future:then、catchError、whenComplete

获取 Future 结果最基础的方法是注册回调:

fetchData()
    .then((value) => print('成功:$value'))
    .catchError((error) => print('失败:$error'))
    .whenComplete(() => print('无论成败都会执行'));
  • then:成功时触发,接收结果值。
  • catchError:发生异常时触发,可接收异常对象。
  • whenComplete:总是执行,常用于清理资源。

链式调用可以让多个异步操作串行执行,每个 then 的返回值会被包装成新的 Future

async 与 await:像同步代码一样写异步

asyncawait 是 Dart 最优雅的异步处理方式。在函数体前添加 async 关键字,函数会自动返回一个 Future。内部使用 await 可以暂停当前函数的执行,直到 Future 完成,然后直接取得结果。

Future<void> loadData() async {
  try {
    String data = await fetchData();
    print('数据:$data');
  } catch (e) {
    print('出错:$e');
  } finally {
    print('操作结束');
  }
}
  • await 只能在 async 函数中使用。
  • 使用 try-catch-finally 即可处理异常,就像处理同步代码一样。
  • 多个 Future 可以同时发起请求,再用 await 一起等待,提升并发性能:
Future<void> loadMultiple() async {
  final futureA = fetchDataFromA();   // 立即启动
  final futureB = fetchDataFromB();   // 立即启动
  final resultA = await futureA;
  final resultB = await futureB;
}

Stream:连续的数据流

Stream<T> 表示一系列异步事件或数据。它适合处理连续到达的值,比如用户点击、文件读取进度、WebSocket 消息等。Stream 可以是“单订阅”(single-subscription)或“广播”(broadcast)模式。

创建 Stream

Dart 提供了多种创建 Stream 的方式:

1. 使用异步生成器 async*

这是最直观的方法,使用 yield 逐步发出数据:

Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

2. 使用 StreamController

手动控制数据的添加、错误和关闭:

StreamController<String> controller = StreamController<String>();

// 添加数据
controller.add('第一条消息');
controller.add('第二条消息');
// 添加错误
controller.addError(Exception('模拟错误'));
// 关闭流
controller.close();

// 获取 Stream
Stream<String> stream = controller.stream;

对于广播流,使用 StreamController.broadcast() 允许多个监听者。

监听 Stream:listen

Stream 激活前不会执行任何计算。调用 listen 会返回一个 StreamSubscription 对象,可用于控制订阅(暂停、恢复、取消)。

StreamSubscription<int> subscription = countStream(5).listen(
  (data) {
    print('收到:$data');
  },
  onError: (error) {
    print('错误:$error');
  },
  onDone: () {
    print('流已关闭');
  },
);

你可以在适当时候调用 subscription.pause()subscription.resume()subscription.cancel()

使用 await for 循环

如果只需要顺序处理流中的每个事件,可以使用 await for

Future<void> processStream() async {
  await for (var value in countStream(3)) {
    print('处理:$value');
  }
  print('流处理结束');
}

注意:await for 会暂停函数的执行直到每次有新事件到达,适合单订阅流。

Stream 常用操作符

Stream 提供了大量类似集合的操作方法,可以帮助你转换、过滤、组合数据流。

方法 说明
map<T> 将每个事件转换为另一种类型
where 过滤掉不符合条件的事件
expand<T> 将每个事件扩展为多个事件
take 只取前 n 个事件
skip 跳过前 n 个事件
distinct 过滤掉连续重复的事件
transform 应用一个 StreamTransformer
handleError 拦截并处理错误,阻止流关闭
timeout 为每个事件设置超时
asyncMap 对每个事件执行异步转换
asyncExpand 异步扩展事件

链式调用示例

Stream<int> numbers = Stream.periodic(Duration(seconds: 1), (i) => i + 1);

numbers
    .where((n) => n % 2 == 0)       // 只保留偶数
    .map((n) => '偶数:$n')         // 转换为字符串
    .take(3)                         // 只取前3个
    .listen(print);

错误处理

Future 错误处理

使用 try-catchcatchError 都可以捕获 Future 链中的错误。未捕获的异常会导致程序崩溃。一定要处理错误,尤其是在结合 await 时,记得包裹在 try 块中。

Stream 错误处理

Stream 中的错误会通过 StreamSubscriptiononError 回调处理,或者被 handleError 拦截。如果没有错误处理,单订阅流会直接关闭,广播流则会继续接受后续事件。

stream.handleError((error) {
  print('捕获到错误:$error');
}).listen(null);

Future 与 Stream 的相互转换

有些场景需要从一个列表构建流,或者把流的全部事件收集成一个列表。Dart 提供了内置的支持。

  • Stream.fromFuture:将单个 Future 包装成一个流。
  • Stream.fromFutures:从一组 Future 创建流。
  • Stream.fromIterable:从集合创建同步数据流。
  • Future.wait:并发执行多个 Future,并返回所有结果的列表。
  • Stream.toList():将流的所有事件收集到一个列表(返回 Future<List<T>>)。
  • Stream.toSet()Stream.length 等。

实际应用场景

网络请求(Future)

Future<http.Response> fetchPost() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
  if (response.statusCode == 200) {
    return response.body;
  } else {
    throw Exception('加载失败');
  }
}

用户交互防抖(Stream)
对按钮点击事件流进行过滤,防止快速多次触发。

button.onClick
    .debounceTime(Duration(milliseconds: 300))
    .listen((_) => submitForm());

文件读取进度(Stream)
通过 Stream 逐块读取大文件,实时更新进度条。

总结

  • Future 处理“一次性的异步返回值”,用 thenawait 消费结果。
  • Stream 处理“多次推送的异步事件序列”,用 listenawait for 订阅。
  • 始终处理错误,避免未捕获的异常导致应用中断。
  • 熟练使用 async/await 可以让异步代码清晰易读,接近同步编码体验。
  • 掌握 Stream 的操作符,能极大提高数据处理效率。

理解 Future 和 Stream 是 Dart 异步编程的基石,也是 Flutter 应用开发中不可或缺的技能。通过大量实践,你将逐渐习惯事件循环的工作方式,编写出高效、流畅的异步代码。