Future

Future 代表了一个“在未来某个时间点”才会完成的操作结果。 Future 的生命周期可分为两个阶段:

  • 未完成 (Uncompleted):异步操作正在进行中,结果尚未产生;
  • 已完成 (Completed):操作结束,会有两种可能的结果:
    • 成功 (Value):操作顺利完成,返回具体的数据(如 Future 返回字符串);
    • 失败 (Error):操作中途出错,返回一个异常。
stateDiagram
    [*] --> Uncompleted : Create Future
   
    Uncompleted --> Success : Value
    Uncompleted --> Failure : Error

    Success --> [*] : .then / await
    Failure --> [*] : .catchError / try-catch

示例

先通过一个例子来看同步函数与异步函数及其使用上的区别:

// ex721.dart
import 'dart:async';
import 'dart:io';

void main() {
  final watch = Stopwatch()..start(); // 3
  var count = 0;
  void myprint(o) =>
      print('out${count++} ${watch.elapsed.inMilliseconds}ms: $o'); // 3a

  myprint('before compute');
  var result = compute(3, 4); // 4
  myprint('after compute');
  myprint('result=$result');

  myprint('before computeAsync');
  var futureResult = computeAsync(5, 12); // 5
  myprint('after computeAsync');

  futureResult.then((onValue) => myprint('result=$onValue')); // 6

  /* An example output:
out0 0ms: before compute
out1 1004ms: after compute
out2 1005ms: result=7
out3 1005ms: before computeAsync
out4 1010ms: after computeAsync
out5 2020ms: result=17
*/
}

// 1
int compute(int a, int b) {
  // Simulate a long task
  sleep(Duration(seconds: 1));
  return a + b;
}

// 2
Future<int> computeAsync(int a, int b) {
  return Future(() => compute(a, b));
}
  1. compute 是一个同步函数,它模拟了一个耗时约1秒种的计算任务(根据ab进行计算),然后返回其结果;
  2. computeAsynccompute的异步版本,它是一个异步函数,它使用Future.new创建了一个Future对象并返回;
  3. Stopwatch是一个用于计时对秒表,精确到微秒(microseconds),该示例使用了毫秒(milliseconds)作为计时单位(3a);
  4. 调用compute函数,程序阻塞直到compute返回结果(out0 - out2);
  5. 调用computeAsync函数得到的是一个Future对象,computeAsync函数返回时,真正的计算(compute)还在进行中,即程序不会挂起等待计算结果(out3 - out4);
  6. futureResult.then提供一个callback(回调)函数,当computeAsync内部计算完成时,callback将被调用(out5)。

同步 vs 异步:

特性同步函数 (compute)异步函数 (computeAsync)
定义方式int compute(...)Future<int> computeAsync(...)
返回值直接返回结果返回一个“凭证” Future,承诺以后给结果
执行表现调用时程序会“停”在这一行直到算完调用后程序可以立即去做别的事
调用方式var res = compute(3, 4);computeAsync(3, 4).then(...);

async/wait

上述示例中的computeAsync可以用asyc/wait改写,这是更现代、更具可读性的风格,让异步代码看起来像同步代码。

// ex722.dart
void main() async { // 3
  var result = await compute(8, 15); // 2
  print(result); // Output: 23
}

// 1
Future<int> compute(int a, int b) async => a + b;
  1. async写在函数(或方法)签名的最后,这里标记compute为异步函数;
  2. 调用异步函数compute时,使用前缀关键字await,使得异步代码像同步代码那样简洁;
  3. await 关键字只能在异步函数(或方法)中。

异常处理

下面的例子演示了有关Future的异常处理。

// ex723.dart
void main() async {
  divide(2, 1).then((val) => print(val)); // Output: 2

  divide(2, 0).then((val) => print(val)).catchError((e) => print(e));
  // Output: IntegerDivisionByZeroException

  print(await divide(3, 1)); // Output: 3

  try {
    print(await divide(3, 0));
  } on Exception catch (e) {
    print(e); // Output: IntegerDivisionByZeroException
  }
}

Future<int> divide(int a, int b) async => a ~/ b;

构造函数

常用的 Future 构造函数:

  • Futrue(computation)Futrue.new) 使用 Timer.run 创建一个包含computation()的计算结果的Future(异步执行)
  • Futrue.sync(computation) 创建一个包含computation()的计算结果的Future(同步执行);
  • Future.delayed(duration,[computation]) 延迟一段时间后执行代码(常用于模拟网络延迟)
  • Future.value([value]) 立即创建一个成功状态的 Future
  • Future.error(error, [stackTrace]) 立即创建一个失败状态的 Future
  • Future.microtask(computation) 将任务放入微任务队列(Microtask Queue),相比Futrue.new优先级更高

请仔细阅读并分析如下示例。

// ex724.dart
void main() {
  final watch = Stopwatch()..start();
  var count = 0;
  void myprint(o) =>
      print('out${count++} ${watch.elapsed.inMilliseconds}ms: $o');

  myprint('start');
  Future.delayed(Duration(seconds: 1), () => myprint('deplay'));
  Future.microtask(() => 'microtask1').then(myprint);
  Future(() => 'new').then(myprint);
  Future.sync(() => 'sync').then(myprint);
  Future.value('value').then(myprint);
  Future.error('error').catchError(myprint);
  Future.microtask(() => 'microtask2').then(myprint);

  /* An example output:
out0 0ms: start
out1 24ms: microtask1
out2 24ms: sync
out3 25ms: value
out4 27ms: error
out5 28ms: microtask2
out6 30ms: new
out7 1014ms: deplay
*/
}

实用方法

Future提供了下列实用的静态方法:

  • Future.wait(futures,{...}) 同时启动多个Future,并等待它们全部完成;
  • Future.any(futures) 谁先完成就返回谁(无论是成功还是失败);
  • Future.doWhile(action) 重复执行异步操作,直到返回 false 为止。
  • Future.forEach(elements, action) 对集合中的每个元素顺序执行异步操作,它会等前一个执行完再开始下一个。

下面的示例代码不难理解,请读者自行分析。

// ex725.dart
import 'dart:math';

void main() {
  final watch = Stopwatch()..start();
  var count = 0;
  void myprint(o) =>
      print('out${count++} ${watch.elapsed.inMilliseconds}ms: $o');

  Future.wait([
    Future(() => myprint('wait1')),
    Future(() => myprint('wait2')),
    Future(() => myprint('wait3')),
  ]);

  Future.wait([
    Future(() => 1),
    Future(() => 2),
    Future(() => 3),
  ]).then(myprint);

  Future.any([
    Future.delayed(Duration(milliseconds: 100), () => myprint('any1')),
    Future.delayed(Duration(milliseconds: 100), () => myprint('any2')),
    Future.delayed(Duration(milliseconds: 100), () => myprint('any3')),
  ]);

  Future.any([
    Future.delayed(Duration(milliseconds: 100), () => 4),
    Future.delayed(Duration(milliseconds: 100), () => 5),
    Future.delayed(Duration(milliseconds: 100), () => 6),
  ]).then(myprint);

  final rand = Random();
  Future.doWhile(() {
    var d = rand.nextDouble();
    myprint(d);
    return d < 0.8;
  });

  Future.forEach(['A', 'B', 'C'], myprint);

  /* An example output:
out0 13ms: 0.06789414914959846
out1 15ms: 0.142393000844255
out2 15ms: 0.7807443343674055
out3 15ms: 0.2542896840253688
out4 15ms: 0.5590822258793036
out5 15ms: 0.8071049146003575
out6 17ms: A
out7 17ms: B
out8 17ms: C
out9 19ms: wait1
out10 21ms: wait2
out11 21ms: wait3
out12 24ms: [1, 2, 3]
out13 110ms: any1
out14 113ms: any2
out15 113ms: any3
out16 114ms: 4
   */
}

Timeout(超时)

Future.timeout()实例方法是一个实用的守时工具。它允许我们为一个异步操作设置一个最大容忍时间(timeLimit)。简单来说,它的逻辑是:“要么在规定时间内给我结果,要么我就抛出异常走人。”

// ex726.dart
void main() async {
  var data = await fetchData().timeout(Duration(milliseconds: 2000)); // 1
  print(data); // Output: ok

  data = await fetchData().timeout(Duration(milliseconds: 500)); // 2
  print(data);
  /* Output:
Unhandled exception:
TimeoutException after 0:00:00.500000: Future not completed
*/
}

Future<String> fetchData() async {
  // Simulate a long task
  await Future.delayed(Duration(seconds: 1));
  return 'ok';
}
  1. fetchData()函数在 timeLimit(2s) 到达之前完成了,timeout()方法返回的 Future 会正常返回原任务的结果,就好像 timeout() 从未存在过一样。
  2. timeLimit(0.5s)耗尽时fetchData()函数还没执行完,于是这一行代码抛出了TimeoutException

通过给timeout()提供 onTimeout 参数 ,可以避免遭遇TimeoutExceptiononTimeout 函数返回一个超时的保底值。

// ex727.dart
void main() async {
  var data = await fetchData().timeout(
    Duration(milliseconds: 500),
    onTimeout: () => 'timeout',
  );
  print(data); // Output: timeout
}

Future<String> fetchData() async {
  // ... (omitted for brevity)
}

Reference