跳到主要内容

Future 和错误处理

Dart 语言原生支持异步,这使得异步 Dart 代码更易于阅读和编写。然而,一些代码——特别是旧代码——可能仍在使用 Future 方法,例如 then()catchError()whenComplete()

本页面可以帮助您避免在使用这些 Future 方法时的一些常见陷阱。

Future API 和回调

#

使用 Future API 的函数会注册回调来处理完成 Future 的值(或错误)。例如

dart
myFunc().then(processValue).catchError(handleError);

注册的回调基于以下规则触发:如果 then() 的回调在以值完成的 Future 上被调用,则其回调触发;如果 catchError() 的回调在以错误完成的 Future 上被调用,则其回调触发。

在上面的示例中,如果 myFunc() 的 Future 以值完成,则 then() 的回调触发。如果在 then() 中没有产生新的错误,则 catchError() 的回调不会触发。另一方面,如果 myFunc() 以错误完成,则 then() 的回调不会触发,而 catchError() 的回调会触发。

使用 then() 和 catchError() 的示例

#

链式 then()catchError() 调用是处理 Future 时常见的模式,可以粗略地认为是 try-catch 块的等价物。

接下来的几节将给出这种模式的示例。

将 catchError() 用作全面的错误处理程序

#

以下示例处理从 then() 回调中抛出异常,并演示 catchError() 作为错误处理程序的通用性

dart
myFunc()
    .then((value) {
      doSomethingWith(value);
      ...
      throw Exception('Some arbitrary error');
    })
    .catchError(handleError);

如果 myFunc() 的 Future 以值完成,则 then() 的回调触发。如果 then() 回调中的代码抛出异常(如上例所示),则 then() 的 Future 以错误完成。该错误由 catchError() 处理。

如果 myFunc() 的 Future 以错误完成,则 then() 的 Future 也以该错误完成。该错误也由 catchError() 处理。

无论错误是源自 myFunc() 还是 then()catchError() 都能成功处理它。

then() 中的错误处理

#

为了更细粒度的错误处理,您可以在 then() 中注册第二个(onError)回调来处理以错误完成的 Future。以下是 then() 的签名

dart
Future<R> then<R>(FutureOr<R> Function(T value) onValue, {Function? onError});

仅当您想区分转发给 then() 的错误和 then() 内部生成的错误时,才注册可选的 onError 回调

dart
asyncErrorFunction()
    .then(
      successCallback,
      onError: (e) {
        handleError(e); // Original error.
        anotherAsyncErrorFunction(); // Oops, new error.
      },
    )
    .catchError(handleError); // Error from within then() handled.

在上面的示例中,asyncErrorFunction() 的 Future 错误由 onError 回调处理;anotherAsyncErrorFunction() 导致 then() 的 Future 以错误完成;此错误由 catchError() 处理。

一般来说,不建议实现两种不同的错误处理策略:只有当有充分理由需要在 then() 中捕获错误时,才注册第二个回调。

长链中间的错误

#

通常会有连续的 then() 调用,并使用 catchError() 捕获链中任何部分生成的错误

dart
Future<String> one() => Future.value('from one');
Future<String> two() => Future.error('error from two');
Future<String> three() => Future.value('from three');
Future<String> four() => Future.value('from four');

void main() {
  one() // Future completes with "from one".
      .then((_) => two()) // Future completes with two()'s error.
      .then((_) => three()) // Future completes with two()'s error.
      .then((_) => four()) // Future completes with two()'s error.
      .then((value) => value.length) // Future completes with two()'s error.
      .catchError((e) {
        print('Got error: $e'); // Finally, callback fires.
        return 42; // Future completes with 42.
      })
      .then((value) {
        print('The value is $value');
      });
}

// Output of this program:
//   Got error: error from two
//   The value is 42

在上面的代码中,one() 的 Future 以值完成,但 two() 的 Future 以错误完成。当在以错误完成的 Future 上调用 then() 时,then() 的回调不会触发。相反,then() 的 Future 以其接收者的错误完成。在我们的示例中,这意味着在调用 two() 之后,每个后续 then() 返回的 Future 都以 two() 的错误完成。该错误最终由 catchError() 处理。

处理特定错误

#

如果我们想捕获特定错误怎么办?或者捕获多个错误?

catchError() 接受一个可选的命名参数 test,它允许我们查询抛出的错误类型。

dart
Future<T> catchError(Function onError, {bool Function(Object error)? test});

考虑 handleAuthResponse(params),一个根据提供的参数验证用户并将其重定向到适当 URL 的函数。鉴于复杂的工作流程,handleAuthResponse() 可能会生成各种错误和异常,您应该区别对待它们。以下是如何使用 test 来实现这一点

dart
void main() {
  handleAuthResponse(const {'username': 'dash', 'age': 3})
      .then((_) => ...)
      .catchError(handleFormatException, test: (e) => e is FormatException)
      .catchError(
        handleAuthorizationException,
        test: (e) => e is AuthorizationException,
      );
}

使用 whenComplete() 的异步 try-catch-finally

#

如果 then().catchError() 类似于 try-catch,那么 whenComplete() 就相当于 'finally'。在 whenComplete() 中注册的回调在 whenComplete() 的接收者完成时被调用,无论它是以值完成还是以错误完成

dart
final server = connectToServer();
server
    .post(myUrl, fields: const {'name': 'Dash', 'profession': 'mascot'})
    .then(handleResponse)
    .catchError(handleError)
    .whenComplete(server.close);

无论 server.post() 是产生有效响应还是错误,我们都希望调用 server.close。我们通过将其放入 whenComplete() 来确保这一点。

完成 whenComplete() 返回的 Future

#

如果 whenComplete() 内部没有发出错误,则其 Future 以与调用 whenComplete() 的 Future 相同的方式完成。通过示例最容易理解这一点。

在下面的代码中,then() 的 Future 以错误完成,因此 whenComplete() 的 Future 也以该错误完成。

dart
void main() {
  asyncErrorFunction()
      // Future completes with an error:
      .then((_) => print("Won't reach here"))
      // Future completes with the same error:
      .whenComplete(() => print('Reaches here'))
      // Future completes with the same error:
      .then((_) => print("Won't reach here"))
      // Error is handled here:
      .catchError(handleError);
}

在下面的代码中,then() 的 Future 以错误完成,该错误现在由 catchError() 处理。因为 catchError() 的 Future 以 someObject 完成,所以 whenComplete() 的 Future 也以该对象完成。

dart
void main() {
  asyncErrorFunction()
      // Future completes with an error:
      .then((_) => ...)
      .catchError((e) {
        handleError(e);
        printErrorMessage();
        return someObject; // Future completes with someObject
      })
      .whenComplete(() => print('Done!')); // Future completes with someObject
}

whenComplete() 中产生的错误

#

如果 whenComplete() 的回调抛出错误,则 whenComplete() 的 Future 以该错误完成

dart
void main() {
  asyncErrorFunction()
      // Future completes with a value:
      .catchError(handleError)
      // Future completes with an error:
      .whenComplete(() => throw Exception('New error'))
      // Error is handled:
      .catchError(handleError);
}

潜在问题:未能及早注册错误处理程序

#

错误处理程序在 Future 完成之前安装至关重要:这可以避免 Future 以错误完成、错误处理程序尚未附加以及错误意外传播的情况。考虑这段代码

dart
void main() {
  Future<Object> future = asyncErrorFunction();

  // BAD: Too late to handle asyncErrorFunction() exception.
  Future.delayed(const Duration(milliseconds: 500), () {
    future.then(...).catchError(...);
  });
}

在上面的代码中,catchError() 直到 asyncErrorFunction() 调用半秒后才注册,导致错误未被处理。

如果 asyncErrorFunction()Future.delayed() 回调中调用,问题就会消失

dart
void main() {
  Future.delayed(const Duration(milliseconds: 500), () {
    asyncErrorFunction()
        .then(...)
        .catchError(...); // We get here.
  });
}

潜在问题:意外混合同步和异步错误

#

返回 Future 的函数几乎总是应该在 Future 中发出它们的错误。由于我们不希望此类函数的调用者必须实现多种错误处理场景,因此我们希望防止任何同步错误泄漏出去。考虑这段代码

dart
Future<int> parseAndRead(Map<String, dynamic> data) {
  final filename = obtainFilename(data); // Could throw.
  final file = File(filename);
  return file.readAsString().then((contents) {
    return parseFileData(contents); // Could throw.
  });
}

该代码中有两个函数可能会同步抛出异常:obtainFilename()parseFileData()。由于 parseFileData()then() 回调内部执行,其错误不会从函数中泄漏。相反,then() 的 Future 以 parseFileData() 的错误完成,该错误最终完成 parseAndRead() 的 Future,并且该错误可以由 catchError() 成功处理。

但是 obtainFilename() 不是在 then() 回调中调用的;如果它抛出异常,则会传播一个同步错误

dart
void main() {
  parseAndRead(data).catchError((e) {
    print('Inside catchError');
    print(e);
    return -1;
  });
}

// Program Output:
//   Unhandled exception:
//   <error from obtainFilename>
//   ...

由于使用 catchError() 不会捕获该错误,parseAndRead() 的客户端将为该错误实现单独的错误处理策略。

解决方案:使用 Future.sync() 包装代码

#

确保函数不会意外地抛出同步错误的常见模式是将函数体包装在新 Future.sync() 回调中

dart
Future<int> parseAndRead(Map<String, dynamic> data) {
  return Future.sync(() {
    final filename = obtainFilename(data); // Could throw.
    final file = File(filename);
    return file.readAsString().then((contents) {
      return parseFileData(contents); // Could throw.
    });
  });
}

如果回调返回一个非 Future 值,则 Future.sync() 的 Future 以该值完成。如果回调抛出异常(如上例所示),则 Future 以错误完成。如果回调本身返回一个 Future,则该 Future 的值或错误会完成 Future.sync() 的 Future。

将代码包装在 Future.sync() 中,catchError() 可以处理所有错误

dart
void main() {
  parseAndRead(data).catchError((e) {
    print('Inside catchError');
    print(e);
    return -1;
  });
}

// Program Output:
//   Inside catchError
//   <error from obtainFilename>

Future.sync() 使您的代码能够抵御未捕获的异常。如果您的函数中包含大量代码,则很可能您在不知不觉中正在做一些危险的事情

dart
Future fragileFunc() {
  return Future.sync(() {
    final x = someFunc(); // Unexpectedly throws in some rare cases.
    var y = 10 / x; // x should not equal 0.
    ...
  });
}

Future.sync() 不仅允许您处理您知道可能发生的错误,还可以防止错误意外地从您的函数中泄漏。

更多信息

#

有关 Future 的更多信息,请参阅Future API 参考