How to Handle Exceptions in Dart Asynchronous Functions When try-catch Doesn't Work
Introduction
While trying to catch an exception within a Flutter function and map it to an application-specific error, I encountered a situation where the exception was not caught. However, the exception was caught outside the function.
This issue has to do with how try-catch
works inside asynchronous functions, and I’d like to share my findings.
Problematic Code
Consider a function like someAsyncService
, which calls fetchFromAPI
to fetch data and return it directly. To catch any exceptions thrown by fetchFromAPI
, I used a try-catch
block in someAsyncService
.
Future<String> fetchFromAPI() async {
await Future.delayed(Duration(seconds: 1));
throw Exception('error api');
}
Future<String> someAsyncService() {
try {
return fetchFromAPI();
} catch (exception) {
print('someAsyncService: $exception');
rethrow;
}
}
void main() async {
try {
final result = await someAsyncService();
print(result);
} catch (exception) {
print('main: $exception');
rethrow;
}
}
However, when running the code, no output indicates that the exception was caught in someAsyncService
.
main: Exception: error api
Solution
The solution to this issue is to await
the fetchFromAPI()
call inside someAsyncService
. The corrected code is as follows:
Future<String> someAsyncService() async {
try {
final result = await fetchFromAPI();
return result;
// Alternatively, you can also use `return await fetchFromAPI();`
} catch (exception) {
print('someAsyncService: $exception');
rethrow;
}
}
Now, the expected output is obtained:
someAsyncService: Exception: error
main: Exception: error
Root Cause
The root cause of this issue is that the asynchronous function fetchFromAPI
was not awaited, so by the time it threw an exception, someAsyncService
had already completed, making it impossible to catch the exception.
The documentation also highlights the importance of awaiting asynchronous code. Essentially, to handle both successful and erroneous results from a Future
(asynchronous function), you must await it.
In general, when writing asynchronous code, you should always await a future when it is produced, and not wait until after another asynchronous delay. That ensures that you are ready to receive any error that the future might produce, which is important because an asynchronous error that no-one is awaiting is an uncaught error and may terminate the running program.
Conclusion
Generally, it’s safe to assume that you should await
asynchronous code to avoid such issues. However, in functions that don’t use async
, forgetting to await
won’t produce lint errors, making it easy to overlook. Other solutions might include creating custom lint rules or writing comprehensive tests.