I am designing an API and would like to handle a situation when a user marks a void function async by mistake.
The simplified code is listed below:
void test(void Function() run) {
try {
for (var i = 0; i < 3; i++) {
print(i);
run();
}
} catch (e) {
print(e);
} finally {
print('in finally block.');
}
}
void errorThrower(String message) {
print('in errorThrower ');
throw message;
}
void main(List<String> args) {
test(() async{ // <-------- marked async by API user's mistake
print('in run ...');
errorThrower('error thrown in test');
});
}
If the function is not marked async the program output is as expected and the error is
thrown, caught, handled, and the finally block is executed:
$ dart main.dart
0
in run ...
in errorThrower
error thrown in test
in finally block.
However, if the function is marked async the console output is:
$ dart main.dart
0
in run ...
in errorThrower
1
in run ...
in errorThrower
2
in run ...
in errorThrower
in finally block.
Unhandled exception:
error thrown in test
#0 errorThrower (file:///home/dan/WORK/DartProjects/benchmark_runner/bin/throws_test.dart:16:3)
#1 main.<anonymous closure> (file:///main.dart:22:5)
#2 test (file:///main.dart:5:10)
#3 main (file:///main.dart:20:3)
#4 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:294:33)
#5 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)
What is going on? I tried a debugger, the program steps into run() then errorThrower and then the loop continues.
It looks like an unawaited future error but I don't know how to catch and handle it since a void function cannot be awaited.
Is there any way I can catch and handle the error without changing the signature o f run()?
Functions marked with
asyncare automatically transformed so that return values and thrown objects are wrapped inFutures. FailedFutures must be handled by registering a callback withFuture.catchError, by using usingawaiton theFuturewithin atryblock (which is syntactic sugar for registering aFuture.catchErrorcallback), or by setting up aZoneerror handler (which I'll consider to be outside the scope of this answer). Conceptually you can consider a failedFutureas throwing an exception from the Dart event loop, after your synchronoustestfunction has already left itstry-catchblock.A
T Function()is a subtype of (is substitutable for)void Function(). Therefore at compile-time you cannot prevent aFuture<void> Function()from being passed where avoid Function()is expected. This is also why things likeIterable.forEachcan't prevent asynchronous callbacks (even though it's almost always a bad idea) and would need support from the linter.You could add a runtime check:
Or if you just want to swallow the error:
Note that if you want
testto wait forrunto complete (successfully or not), thentestitself would need to be asynchronous and would need to return aFuture. At that point, you might as well always assume thatrunmight be asynchronous and unconditionallyawaitit: