-
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));
}