期货和错误处理
- Future API 和回调
- then() 与 catchError() 结合使用的示例
- 使用 whenComplete() 进行异步 try-catch-finally
- 潜在问题:未能及早注册错误处理程序
- 潜在问题:意外混合同步错误和异步错误
- 更多信息
Dart 语言具有原生的 异步支持,使得异步 Dart 代码更易于阅读和编写。但是,某些代码(尤其是较旧的代码)可能仍然使用 Future 方法,例如 then()
、catchError()
和 whenComplete()
。
此页面可以帮助您在使用这些 Future 方法时避免一些常见陷阱。
Future API 和回调
#使用 Future API 的函数注册回调,以处理完成 Future 的值(或错误)。例如
myFunc().then(processValue).catchError(handleError);
注册的回调基于以下规则触发:如果 then()
在使用值完成的 Future 上调用,则触发其回调;如果 catchError()
在使用错误完成的 Future 上调用,则触发其回调。
在上面的示例中,如果 myFunc()
的 Future 使用值完成,则触发 then()
的回调。如果在 then()
中未产生新的错误,则不会触发 catchError()
的回调。另一方面,如果 myFunc()
使用错误完成,则不会触发 then()
的回调,而会触发 catchError()
的回调。
then() 与 catchError() 结合使用的示例
#处理 Future 时,链接的 then()
和 catchError()
调用是一种常见模式,可以被视为 try-catch 块的粗略等效项。
接下来的几节将提供此模式的示例。
catchError() 作为综合错误处理程序
#以下示例处理从 then()
的回调中抛出异常的情况,并演示了 catchError()
作为错误处理程序的多功能性
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()
的签名
Future<R> then<R>(FutureOr<R> Function(T value) onValue, {Function? onError});
仅当您想区分转发到 then()
的错误和在 then()
中生成的错误时,才注册可选的 onError 回调
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()
捕获从链的任何部分生成的错误
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
,它允许我们查询抛出的错误类型。
Future<T> catchError(Function onError, {bool Function(Object error)? test});
考虑 handleAuthResponse(params)
,这是一个根据提供的 params 对用户进行身份验证并将其重定向到适当 URL 的函数。鉴于复杂的工作流,handleAuthResponse()
可能会生成各种错误和异常,您应该以不同的方式处理它们。以下是如何使用 test
来实现此目的
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()
中注册的回调
final server = connectToServer();
server
.post(myUrl, fields: const {'name': 'Dash', 'profession': 'mascot'})
.then(handleResponse)
.catchError(handleError)
.whenComplete(server.close);
我们希望调用 server.close
,无论 server.post()
产生一个有效响应还是一个错误。我们通过将其置于 whenComplete()
中来确保发生这种情况。
完成 whenComplete() 返回的 Future
#如果 whenComplete()
中没有发出错误,则其 Future 的完成方式与调用 whenComplete()
的 Future 的完成方式相同。通过示例最容易理解这一点。
在下面的代码中,then()
的 Future 以一个错误完成,因此 whenComplete()
的 Future 也以该错误完成。
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 以相同的对象完成。
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 将以该错误完成
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 以错误完成、错误处理程序尚未附加且错误意外传播的情况。考虑这段代码
void main() {
Future<Object> future = asyncErrorFunction();
// BAD: Too late to handle asyncErrorFunction() exception.
Future.delayed(const Duration(milliseconds: 500), () {
future.then(...).catchError(...);
});
}
在上面的代码中,直到调用 asyncErrorFunction()
半秒后才注册 catchError()
,并且错误未得到处理。
如果在 Future.delayed()
回调中调用 asyncErrorFunction()
,则问题将消失
void main() {
Future.delayed(const Duration(milliseconds: 500), () {
asyncErrorFunction()
.then(...)
.catchError(...); // We get here.
});
}
潜在问题:意外混合同步错误和异步错误
#返回 Future 的函数几乎总是在 future 中发出它们的错误。由于我们不希望此类函数的调用者必须实现多个错误处理方案,因此我们希望防止任何同步错误泄漏。考虑这段代码
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()
回调中调用;如果 它 抛出,则会传播一个同步错误
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()
回调中
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()
可以处理所有错误
void main() {
parseAndRead(data).catchError((e) {
print('Inside catchError');
print(e);
return -1;
});
}
// Program Output:
// Inside catchError
// <error from obtainFilename>
Future.sync()
使你的代码能够抵御未捕获的异常。如果你的函数中包含大量代码,那么你很可能在不知不觉中做了一些危险的事情
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 API 参考 以获取有关 Future 的更多信息。