跳到主要内容

dart:async

异步编程通常使用回调函数,但 Dart 提供了替代方案:Future 和 Stream 对象。Future 就像一个承诺,表示将来会提供一个结果。Stream 是一种获取一系列值(例如事件)的方式。Future、Stream 以及更多内容都在 dart:async 库中(API 参考)。

dart:async 库在 Web 应用和命令行应用中都有效。要使用它,请导入 dart:async

dart
import 'dart:async';

Future

#

Future 对象在整个 Dart 库中都很常见,通常作为异步方法返回的对象。当 future 完成时,它的值就可以使用了。

使用 await

#

在您直接使用 Future API 之前,请考虑使用 await 代替。使用 await 表达式的代码可能比使用 Future API 的代码更容易理解。

考虑以下函数。它使用 Future 的 then() 方法按顺序执行三个异步函数,等待每个函数完成之后再执行下一个。

dart
void runUsingFuture() {
  // ...
  findEntryPoint()
      .then((entryPoint) {
        return runExecutable(entryPoint, args);
      })
      .then(flushThenExit);
}

使用 await 表达式的等效代码看起来更像同步代码

dart
Future<void> runUsingAsyncAwait() async {
  // ...
  var entryPoint = await findEntryPoint();
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
}

async 函数可以捕获来自 Futures 的异常。例如

dart
var entryPoint = await findEntryPoint();
try {
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
} catch (e) {
  // Handle the error...
}

有关使用 await 和相关 Dart 语言功能的更多信息,请参阅异步编程教程

基本用法

#

您可以使用 then() 来安排在 future 完成时运行的代码。例如,Client.read() 返回一个 Future,因为 HTTP 请求可能需要一段时间。使用 then() 可以让您在该 Future 完成并且承诺的字符串值可用时运行一些代码

dart
httpClient.read(url).then((String result) {
  print(result);
});

使用 catchError() 来处理 Future 对象可能抛出的任何错误或异常。

dart
httpClient
    .read(url)
    .then((String result) {
      print(result);
    })
    .catchError((e) {
      // Handle or ignore the error.
    });

then().catchError() 模式是 try-catch 的异步版本。

链式调用多个异步方法

#

then() 方法返回一个 Future,这提供了一种以特定顺序运行多个异步函数的有用方法。如果使用 then() 注册的回调返回一个 Future,则 then() 返回一个 Future,该 Future 将以与回调返回的 Future 相同的结果完成。如果回调返回任何其他类型的值,则 then() 会创建一个新的 Future,该 Future 以该值完成。

dart
Future result = costlyQuery(url);
result
    .then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done!'))
    .catchError((exception) {
      /* Handle exception... */
    });

在前面的示例中,方法按以下顺序运行

  1. costlyQuery()
  2. expensiveWork()
  3. lengthyComputation()

这是使用 await 编写的相同代码

dart
try {
  final value = await costlyQuery(url);
  await expensiveWork(value);
  await lengthyComputation();
  print('Done!');
} catch (e) {
  /* Handle exception... */
}

等待多个 futures

#

有时您的算法需要调用多个异步函数,并等待它们全部完成后才能继续。使用 Future.wait() 静态方法来管理多个 Futures 并等待它们完成

dart
Future<void> deleteLotsOfFiles() async =>  ...
Future<void> copyLotsOfFiles() async =>  ...
Future<void> checksumLotsOfOtherFiles() async =>  ...

await Future.wait([
  deleteLotsOfFiles(),
  copyLotsOfFiles(),
  checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');

Future.wait() 返回一个 future,该 future 在所有提供的 futures 完成后完成。它要么以它们的结果完成,要么在任何提供的 future 失败时以错误完成。

处理多个 futures 的错误

#

您也可以等待 futures 的可迭代对象记录上的并行操作。

这些扩展返回一个 future,其中包含所有提供的 futures 的结果值。与 Future.wait 不同,它们还允许您处理错误。

如果集合中的任何 future 以错误完成,wait 将以 ParallelWaitError 完成。这允许调用者处理各个错误并在必要时处置成功的结果。

当您不需要来自每个 future 的结果值时,请在 futures 的可迭代对象上使用 wait

dart
void main() async {
  Future<void> delete() async =>  ...
  Future<void> copy() async =>  ...
  Future<void> errorResult() async =>  ...
  
  try {
    // Wait for each future in a list, returns a list of futures:
    var results = await [delete(), copy(), errorResult()].wait;

    } on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {

    print(e.values[0]);    // Prints successful future
    print(e.values[1]);    // Prints successful future
    print(e.values[2]);    // Prints null when the result is an error

    print(e.errors[0]);    // Prints null when the result is successful
    print(e.errors[1]);    // Prints null when the result is successful
    print(e.errors[2]);    // Prints error
  }

}

当您确实需要来自每个 future 的各个结果值时,请在 futures 的记录上使用 wait。这提供了额外的优势,即 futures 可以是不同的类型

dart
void main() async {
  Future<int> delete() async =>  ...
  Future<String> copy() async =>  ...
  Future<bool> errorResult() async =>  ...

  try {    
    // Wait for each future in a record, returns a record of futures:
    (int, String, bool) result = await (delete(), copy(), errorResult()).wait;
  
  } on ParallelWaitError<(int?, String?, bool?),
      (AsyncError?, AsyncError?, AsyncError?)> catch (e) {
    // ...
    }

  // Do something with the results:
  var deleteInt  = result.$1;
  var copyString = result.$2;
  var errorBool  = result.$3;
}

Stream

#

Stream 对象在整个 Dart API 中都很常见,表示数据序列。例如,HTML 事件(如按钮点击)是使用 streams 传递的。您还可以将文件作为 stream 读取。

使用异步 for 循环

#

有时您可以使用异步 for 循环 (await for) 而不是使用 Stream API。

考虑以下函数。它使用 Stream 的 listen() 方法订阅文件列表,传入一个函数字面量来搜索每个文件或目录。

dart
void main(List<String> arguments) {
  // ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = Directory(searchPath);
      startingDir.list().listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(File(searchPath), searchTerms);
    }
  });
}

使用 await 表达式(包括异步 for 循环 (await for))的等效代码看起来更像同步代码

dart
void main(List<String> arguments) async {
  // ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = Directory(searchPath);
    await for (final entity in startingDir.list()) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(File(searchPath), searchTerms);
  }
}

有关使用 await 和相关 Dart 语言功能的更多信息,请参阅异步编程教程

监听 stream 数据

#

要获取到达的每个值,请使用 await for 或使用 listen() 方法订阅 stream

dart
// Add an event handler to a button.
submitButton.onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

在此示例中,onClick 属性是由提交按钮提供的 Stream 对象。

如果您只关心一个事件,可以使用 firstlastsingle 等属性来获取它。要在处理事件之前对其进行测试,请使用 firstWhere()lastWhere()singleWhere() 等方法。

如果您关心事件的子集,可以使用 skip()skipWhile()take()takeWhile()where() 等方法。

转换 stream 数据

#

通常,您需要在使用 stream 的数据之前更改其格式。使用 transform() 方法生成具有不同数据类型的 stream

dart
var lines = inputStream
    .transform(utf8.decoder)
    .transform(const LineSplitter());

此示例使用了两个转换器。首先,它使用 utf8.decoder 将整数 stream 转换为字符串 stream。然后,它使用 LineSplitter 将字符串 stream 转换为单独行的 stream。这些转换器来自 dart:convert 库(请参阅dart:convert 部分)。

处理错误和完成

#

您如何指定错误和完成处理代码取决于您是使用异步 for 循环 (await for) 还是 Stream API。

如果您使用异步 for 循环,请使用 try-catch 来处理错误。在 stream 关闭后执行的代码放在异步 for 循环之后。

dart
Future<void> readFileAwaitFor() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = inputStream
      .transform(utf8.decoder)
      .transform(const LineSplitter());
  try {
    await for (final line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

如果您使用 Stream API,请通过注册 onError 监听器来处理错误。通过注册 onDone 监听器来运行 stream 关闭后的代码。

dart
var config = File('config.txt');
Stream<List<int>> inputStream = config.openRead();

inputStream
    .transform(utf8.decoder)
    .transform(const LineSplitter())
    .listen(
      (String line) {
        print('Got ${line.length} characters from stream');
      },
      onDone: () {
        print('file is now closed');
      },
      onError: (e) {
        print(e);
      },
    );

更多信息

#

有关在命令行应用中使用 Future 和 Stream 的一些示例,请查看 dart:io 文档。另请参阅以下文章和教程