-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Update April 3, 2024
The Future.onError extension method now solves this problem--that is, this code works without throwing an Error:
Future<void> main() async {
await func().onError(handler);
}
Null handler(_, __) => null;
Future<Object?> func() => Future<bool>.error('oops');
Consider the following code:
Future<void> main() async {
await func().catchError(handler);
}
Null handler(_, __) => null;
Future<Object?> func() => Future<bool>.error('oops');
This code appears to be statically correct. Because the return type of handler
satisfies the return type of func()
, it looks like this code should work. However, at runtime, the .catchError
method will actually be called on a Future<bool>
, and now our handler
function is no longer a valid callback to Future<bool>.catchError()
, and we get an ArgumentError
:
Unhandled exception:
Invalid argument(s) (onError): The error handler of Future.catchError must return a value of the future's type
#0 _FutureListener.handleError (dart:async/future_impl.dart:181:7)
#1 Future._propagateToListeners.handleError (dart:async/future_impl.dart:779:47)
#2 Future._propagateToListeners (dart:async/future_impl.dart:800:13)
#3 Future._completeError (dart:async/future_impl.dart:575:5)
#4 Future._asyncCompleteError.<anonymous closure> (dart:async/future_impl.dart:666:7)
#5 _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#6 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
#7 _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:123:13)
#8 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:5)
Even though the onError
extension method features a strictly-typed callback, it still leads to a runtime-only error:
Future<void> main() async {
await func().onError(handler);
}
Null handler(_, __) => null;
Future<Object?> func() => Future<bool>.error('oops');
Leads to the same stacktrace.
Interestingly, passing our handler to the onError
parameter of .then()
does not have the runtime error:
Future<void> main() async {
await func().then(
(Object? obj) => obj,
onError: handler,
);
}
Null handler(_, __) => null;
Future<Object?> func() => Future<bool>.error('oops');
This has lead to a series of very difficult to track down crashes in the Flutter CLI tool (especially since the stacktrace is opaque), such as flutter/flutter#114031
In this particular case, we were doing essentially:
import 'dart:io' as io;
Future<void> main() async {
final process = await io.Process.start('interactive-script.sh', <String>[]);
// stdin is an IOSink
process.stdin
// IOSink.addStream's static type is Future<dynamic> Function(Stream<List<int>>)
// however, the actual runtimeType of what is returned is `Future<Socket>`
.addStream(io.stdin)
.catchError((dynamic err, __) => print(err));
}