【译】异步编程:Futures

dart language

Posted by Kinsomy on October 8, 2018

关键点

  • dart是单线程语言
  • 同步代码会阻塞你的程序
  • 使用Future对象来执行异步操作
  • 在async函数里使用await关键字来挂起执行知道一个Future操作完成
  • 或者使用then()方法
  • 在async函数里使用try-catch表达式捕获错误
  • 或者使用catchError()方法
  • 可以使用链式操作future对象来按顺序执行异步函数

dart是一个单线程的编程语言,如果编写了任何阻塞执行线程的代码(例如耗时计算或者I/O),程序就会被阻塞。异步操作可以让你在等待一个操作完成的同时完成其他工作。Dart使用Future对象来进行异步操作。

介绍

先来看一个会导致阻塞的程序代码:

// Synchronous code
void printDailyNewsDigest() {
  var newsDigest = gatherNewsReports(); // Can take a while.
  print(newsDigest);
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}

程序会收集当天的新闻并且打印出来(耗时操作),然后打印一些用户感兴趣的其他信息。

<gathered news goes here>
Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny.
Baseball score: Red Sox 10, Yankees 0

这段代码是有问题的,当printDailyNewsDigest()方法做耗时操作阻塞,剩下的其他代码无论多长时间都只有等到printDailyNewsDigest()返回结果之后才能继续执行。

为了帮助保持应用程序的响应,Dart库的作者在定义可能做耗时工作的函数时用了异步模型。这些函数的返回值是一个future。

future是什么

一个future是一个Future的泛型Future<T>对象,代表了一个异步操作产生的T类型的结果。如果结果的值不可用,future的类型会是Future<void>,当返回一个future的函数被调用了,将会发生如下两件事: 1.这个函数加入待完成的队列并且返回一个未完成的Future对象。 2.接着,当这个操作结束了,Future对象返回一个值或者错误。

编写返回一个future的代码时,有两面两个可选方法:

  • 使用async和await关键字
  • 使用FutureAPI

async 和 await

asyncawait 关键字是Dart语言异步支持的一部分。允许你不使用Future api像编写同步代码一样编写异步代码。一个异步函数的函数体前面要生命async关键字,await关键字仅在异步函数里生效。

下面的代码就是一个使用了Future api的例子。

import 'dart:async';

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then(print);
  // You don't *have* to return the future here.
  // But if you don't, callers can't await it.
}

main() {
  printDailyNewsDigest();
  printWinningLotteryNumbers();
  printWeatherForecast();
  printBaseballScore();
}

printWinningLotteryNumbers() {
  print('Winning lotto numbers: [23, 63, 87, 26, 2]');
}

printWeatherForecast() {
  print("Tomorrow's forecast: 70F, sunny.");
}

printBaseballScore() {
  print('Baseball score: Red Sox 10, Yankees 0');
}

const news = '<gathered news goes here>';
const oneSecond = Duration(seconds: 1);

// Imagine that this function is more complex and slow. :)
Future<String> gatherNewsReports() =>
    Future.delayed(oneSecond, () => news);

main()方法里面printDailyNewsDigest()是一个被调用的,但是因为等待了一秒延迟执行,所以在最后才被打印出来。这个程序的执行顺序如下:

1.程序开始执行

2.main方法调用printDailyNewsDigest(),但是不会立即返回,会调用gatherNewsReports()

3.gatherNewsReports()开始收集新闻同时返回一个Future

4.printDailyNewsDigest()使用then()指定一个Future的响应结果,调用then()返回一个新完成的Future结果作为他的回调。

5.剩下的print方法是同步的会依次执行。

6.当所有的新闻都被收集到了,带有新闻信息的Future被gatherNewsReports()返回。

7.then()方法执行,将future返回的String作为参数传递给print打印。

future.then(print)等同于future.then((newsDigest) => print(newsDigest))

所以printDailyNewsDigest()还可以写成:

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then((newsDigest) {
    print(newsDigest);
    // Do something else...
  });
}

newsDigest是gatherNewsReports()返回的结果。

即时这个Future的类型是Future<void>,也需要传递一个参数给then()的回调,直接用下划线_表示。

Future<void> printDailyNewsDigest() {
  final future = gatherNewsReports();
  return future.then((_) {
    // Code that doesn't use the `_` parameter...
    print('All reports printed.');
  });
}

处理错误

在Future api里,可以使用catchError()捕获错误:

Future<void> printDailyNewsDigest() =>
    gatherNewsReports().then(print).catchError(handleError);

如果请求不到新闻并且失败了,上面的代码将会执行:

1.gatherNewsReports() 完成返回的future携带了error

2.then()方法中的print不会被执行

3.catchError()捕获到处理错误,future被catchError()正常完成并返回,错误不会继续传递下去。

更多细节阅读 Futures and Error Handling

调用多个函数返回futures

有三个函数,expensiveA()expensiveB()expensiveC(), 它们都返回Future对象。你可以顺序调用它们(one by one),或者也可以同时调用三个函数并且当所有结果都返回时再做处理。Future api支持以上的操作。

用then()进行链式调用

按顺序使用then()调用每个方法

expensiveA()
    .then((aValue) => expensiveB())
    .then((bValue) => expensiveC())
    .then((cValue) => doSomethingWith(cValue));

这是个嵌套调用,上一个函数的返回结果作为下一个函数的参数。

使用Future.wait()等待多个方法一起完成

如果好几个函数的执行顺序无关紧要,可以使用Future.wait()

Future.wait([expensiveA(), expensiveB(), expensiveC()])
    .then((List responses) => chooseBestResponse(responses, moreInfo))
    .catchError(handleError);

当传递一个future列表给Future.wait()时,它会立即返回一个Future,但是直到列表里的future都完成的时候,这个Future才会完成,他会返回一个列表里面是每一个future产生的结果数据。

如果任何一个调用的函数产生了错误,都会被catchError()捕获到去处理错误。

相关资料