Future 和錯誤處理
- Future API 和回呼
- 搭配 catchError() 使用 then() 的範例
- 使用 whenComplete() 的非同步 try-catch-finally
- 潛在問題:未能及早註冊錯誤處理常式
- 潛在問題:不小心混合同步和非同步錯誤
- 更多資訊
Dart 語言具有原生非同步支援,使得非同步 Dart 程式碼更易於讀取和撰寫。然而,某些程式碼(尤其是較舊的程式碼)可能仍會使用 Future 方法,例如 then()
、catchError()
和 whenComplete()
。
本頁面可以協助您避免使用這些 Future 方法時的一些常見陷阱。
Future API 和回呼
#使用 Future API 的函式會註冊回呼,以處理完成 Future 的值(或錯誤)。例如
myFunc().then(processValue).catchError(handleError);
已註冊的回呼會根據以下規則觸發:如果對以值完成的 Future 叫用 then()
,則會觸發 then()
的回呼;如果對以錯誤完成的 Future 叫用 catchError()
,則會觸發 catchError()
的回呼。
在上述範例中,如果 myFunc()
的 Future 以值完成,則會觸發 then()
的回呼。如果 then()
內沒有產生新的錯誤,則不會觸發 catchError()
的回呼。另一方面,如果 myFunc()
以錯誤完成,則不會觸發 then()
的回呼,而會觸發 catchError()
的回呼。
搭配 catchError() 使用 then() 的範例
#鏈式 then()
和 catchError()
叫用是處理 Future 時的常見模式,可以視為 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()
半秒後才註冊,因此錯誤未被處理。
如果 asyncErrorFunction()
是在 Future.delayed()
回呼中呼叫,問題就會消失
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 的更多資訊,請參閱 Future API 參考文件。
除非另有說明,否則本網站上的文件反映了 Dart 3.6.0。頁面最後更新於 2024-11-17。 檢視原始碼 或 回報問題。