下面把 Dart/Flutter 里的 Future 体系梳理一遍,配上常用写法、组合方式、在 Flutter UI 中的使用和一些坑位提示。

一、Future 是什么

  • Future<T> 表示“将来某个时刻返回一个 T,或者抛出一个错误”,只完成一次(单次异步结果)。

  • 状态流转:未完成 → 已完成(有值) 或 已完成(错误)。

  • 与 Stream 的区别:Future 只产生一个值;Stream 可以产生多个值。

二、如何创建 Future

  • 使用 async 函数(最常见)

    Future<int> loadCount() async {
      await Future.delayed(const Duration(milliseconds: 300));
      return 42;
      // 如果 throw,则以错误完成
    }

  • 工具构造

    Future.value(42);                 // 直接完成
    Future.error(Exception('oops'));  // 以错误完成
    Future.delayed(Duration(seconds: 1), () => 'done'); // 延时后返回
    Future.microtask(() => heavyCalc()); // 微任务队列
    Future.sync(() => maybeSync());  // 同步执行并包装为 Future
    Future(() => computeNow());      // 放入事件队列异步执行

  • Completer(桥接回调式 API)

    final completer = Completer<String>();
    someCallbackAPI((result, err) {
      if (err != null) completer.completeError(err);
      else completer.complete(result);
    });
    return completer.future;

  • CPU 密集型任务不要用 Future 直接在主 Isolate 跑,避免卡 UI。用 compute 或 Isolate:

    // 适合较简单的耗时函数(顶级/静态函数)
    final result = await compute(parseJson, jsonString);
    
    // 或者更灵活地使用 Isolate(适合特别重的任务)
    // final result = await Isolate.run(() => heavyWork());

三、如何使用 Future(消费结果)

  • async/await(可读性最好)

    try {
      final user = await fetchUser();
      // ...
    } catch (e, st) {
      // 统一异常处理 / 上报
    } finally {
      // 清理收尾
    }

  • 链式 API

    fetchUser()
      .then((u) => save(u))
      .catchError((e) => logError(e))
      .whenComplete(() => print('done'));

  • 超时控制

    final data = await fetchData()
        .timeout(const Duration(seconds: 5), onTimeout: () => 'fallback');

四、组合多个 Future

  • 并发执行(并行)

    final results = await Future.wait([
      fetchA(),
      fetchB(),
      fetchC(),
    ]); // 返回顺序与传入顺序一致

  • 取最先完成的一个

    final first = await Future.any([fetchFast(), fetchSlow()]);

  • 顺序执行(逐个等待)

    for (final url in urls) {
      final html = await fetch(url);
      // 依赖上一个结果的场景
    }
    // 或 Future.forEach(urls, (u) => fetch(u));

五、错误处理要点

  • async/await 下用 try/catch 即可捕获 Future 抛出的错误。

  • then/catchError 链条:catchError 只捕获链上之前的错误,注意链式位置。

  • 不中断但要“忽略”的 Future(不 await)要自己加错误兜底,避免未处理异常泄漏到 Zone:

    () async {
      try { await fireAndForget(); } catch (e) { logError(e); }
    }();

  • 顶层兜底(需要时):runZonedGuarded 或 FlutterError.onError/PlatformDispatcher.instance.onError。

六、在 Flutter 里的使用(FutureBuilder 与状态管理)

  • FutureBuilder(展示加载/错误/完成三态)

    class UserPageState extends State<UserPage> {
      late final Future<User> _future;
    
      @override
      void initState() {
        super.initState();
        _future = fetchUser(); // 别在 build 里新建 Future,避免重复请求
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder<User>(
          future: _future,
          builder: (context, snapshot) {
            if (snapshot.connectionState != ConnectionState.done) {
              return const Center(child: CircularProgressIndicator());
            }
            if (snapshot.hasError) {
              return Center(child: Text('出错:${snapshot.error}'));
            }
            final user = snapshot.data!;
            return Text('Hello, ${user.name}');
          },
        );
      }
    }

  • setState 与异步的典型坑:组件已销毁但异步回调里还在 setState

    Future<void> _load() async {
      final data = await fetch();
      if (!mounted) return; // 或 context.mounted
      setState(() => _data = data);
    }

  • 不要在 build 方法里直接发起异步请求(会在每次重建时重复触发)。放到 initState、事件回调,或用状态管理工具(Provider/Bloc/Riverpod 等)托管。

七、调度与事件循环(简要)

  • Dart 有微任务队列与事件队列:

    • Future.microtask/scheduleMicrotask 加到微任务队列,优先于事件队列执行。

    • Future(...) 和 I/O 回调属于事件队列。

  • 这影响到代码执行顺序和 UI 响应时机,但实际业务中只需记住:不要阻塞主线程;耗时的 CPU 任务丢到 Isolate。

八、可取消吗?

  • Future 本身不可取消;常见策略:

    • 用标志位/请求 token 忽略过期结果;

    • 第三方的 CancelableOperation(package:async);

    • I/O 层支持的取消(如 http 请求的 close/abort);

    • 对于 Isolate,直接 kill 该 isolate。

九、测试提示

  • Widget 测试里异步通常用:

    await tester.pump();             // 推进一帧
    await tester.pumpAndSettle();    // 等待所有动画/微任务完成

  • 纯 Dart 测试可用 fakeAsync 控制时间推进,或直接 await。

十、常见最佳实践速查

  • UI 层尽量用 async/await,库/工具层可用 then/catchError。

  • 并发就用 Future.wait;顺序依赖就按顺序 await。

  • 需要 loading/错误界面就用 FutureBuilder,且缓存 Future,避免重复请求。

  • try/catch 包裹 await,finally 做收尾。

  • 重活上 isolate(compute/Isolate),不要卡主线程。

  • 避免在 dispose 后 setState,检查 mounted/context.mounted。

  • 需要超时就 .timeout;需要降级就 onTimeout 返回兜底。

如果你有具体场景(比如“列表页下拉刷新 + 并发请求 + 超时兜底”或“串行步骤向导、每步依赖上一步结果”),我可以按场景给一份更贴近业务的代码模板。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐