Dart:异步

为什么 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里面的值

在上面的一个案例中,我们直接打印的$bInstance 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)

asyncawait

作用

async 的作用是:

  1. 让一个普通函数自动返回 Future(即使你写的是普通返回值,它会包装成 Future)。
  2. 允许在函数体内使用 await

await 的作用是:

  1. 让当前 async 函数暂停,等 Future 完成后拿到结果。

  2. 写起来像同步代码,避免链式 .then()

  3. 会让当前这个函数在这里暂停执行,直到 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.waitFuture.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 不只会发“数据事件”,还有:

  1. 数据事件(data event) → yield 或者 add 产生的。
  2. 错误事件(error event) → 用 addErrorthrow 触发。
  3. 完成事件(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

×

喜欢就点赞,疼爱就打赏