期貨和錯誤處理
- 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),一個根據提供的參數驗證使用者身分,並將使用者重新導向到適當 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.post() 產生有效回應或錯誤,我們都想要呼叫 server.close。我們透過將其置於 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(...);
});
}在以上程式碼中,catchError() 直到呼叫 asyncErrorFunction() 半秒後才註冊,而錯誤未經處理。
如果在 Future.delayed() 回呼內呼叫 asyncErrorFunction(),問題就會消失
void main() {
Future.delayed(const Duration(milliseconds: 500), () {
asyncErrorFunction()
.then(...)
.catchError(...); // We get here.
});
}潛在問題:意外混合同步和非同步錯誤
#傳回 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 的資訊。