Use onError over catchError `Future.catchError` is more error prone in null safe dart than `onError` because return type of the `Function` callback is not statically enforced and `null` was a common default value which no longer works. Migrate all calls from `catchError` to `onError` to demonstrate best practices. The other usages in this package are mostly on `Future<dynamic>` and mostly look safe, though I can't rule out a type error in unusual situations. There was at least one mistake made in the migration of this library - the code to ignore errors when completing a `CancelableOperation` as an error after it was already canceled would instead cause a type error to surface. Fix this and add a test.
diff --git a/CHANGELOG.md b/CHANGELOG.md index d43d717..0041245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,3 +1,8 @@ +## 2.5.1-dev + +* Fix an unhandled async error when completing a `CancelableCompleter` with a + `Future.error` after it was already canceled. + ## 2.5.0 * Stable release for null safety.
diff --git a/lib/src/cancelable_operation.dart b/lib/src/cancelable_operation.dart index c321078..b1cac55 100644 --- a/lib/src/cancelable_operation.dart +++ b/lib/src/cancelable_operation.dart
@@ -189,7 +189,7 @@ final future = value as Future<T>; if (_isCanceled) { // Make sure errors from [value] aren't top-leveled. - future.catchError((_) {}); + future..then<void>((_) {}).onError((_, __) {}); return; }
diff --git a/lib/src/future_group.dart b/lib/src/future_group.dart index 3a6291f..2968b0b 100644 --- a/lib/src/future_group.dart +++ b/lib/src/future_group.dart
@@ -77,7 +77,7 @@ if (!_closed) return null; if (onIdleController != null) onIdleController.close(); _completer.complete(_values.whereType<T>().toList()); - }).catchError((Object error, StackTrace stackTrace) { + }).onError<Object>((error, stackTrace) { if (_completer.isCompleted) return null; _completer.completeError(error, stackTrace); });
diff --git a/lib/src/null_stream_sink.dart b/lib/src/null_stream_sink.dart index 34b100b..b61b2bc 100644 --- a/lib/src/null_stream_sink.dart +++ b/lib/src/null_stream_sink.dart
@@ -53,7 +53,7 @@ // Don't top-level the error. This gives the user a change to call // [close] or [done], and matches the behavior of a remote endpoint // experiencing an error. - ..catchError((_) {}); + ..onError((_, __) {}); @override void add(T data) {
diff --git a/lib/src/stream_sink_completer.dart b/lib/src/stream_sink_completer.dart index 10e549d..c5a098c 100644 --- a/lib/src/stream_sink_completer.dart +++ b/lib/src/stream_sink_completer.dart
@@ -166,7 +166,7 @@ sink .addStream(_controller!.stream) .whenComplete(sink.close) - .catchError((_) {}); + .onError((_, __) {}); } // If the user has already asked when the sink is done, connect the sink's
diff --git a/lib/src/stream_sink_transformer/handler_transformer.dart b/lib/src/stream_sink_transformer/handler_transformer.dart index d6f188e..5f9e0b4 100644 --- a/lib/src/stream_sink_transformer/handler_transformer.dart +++ b/lib/src/stream_sink_transformer/handler_transformer.dart
@@ -101,7 +101,7 @@ _SafeCloseSink(StreamSink<T> inner) : super(inner); @override - Future close() => super.close().catchError((_) {}); + Future close() => super.close().onError((_, __) {}); } /// A function to pass as a [StreamTransformer]'s `handleDone` callback.
diff --git a/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart b/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart index 2afcbff..373a650 100644 --- a/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart +++ b/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart
@@ -40,7 +40,7 @@ // Ignore any errors that come from this call to [_inner.close]. The // user can access them through [done] or the value returned from // [this.close], and we don't want them to get top-leveled. - _inner.close().catchError((_) {}); + _inner.close().onError((_, __) {}); }); }
diff --git a/pubspec.yaml b/pubspec.yaml index 3c8dd95..47c653f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -1,5 +1,5 @@ name: async -version: 2.5.0 +version: 2.5.1-dev description: Utility functions and classes related to the 'dart:async' library. homepage: https://www.github.com/dart-lang/async
diff --git a/test/cancelable_operation_test.dart b/test/cancelable_operation_test.dart index 0e8d8d4..ebb8f49 100644 --- a/test/cancelable_operation_test.dart +++ b/test/cancelable_operation_test.dart
@@ -195,6 +195,13 @@ expect(() => completer.complete(1), throwsStateError); }); + test('ignores completion errors after canceled', () async { + var completer = CancelableCompleter<int>(); + completer.operation.value.whenComplete(expectAsync0(() {}, count: 0)); + await completer.operation.cancel(); + completer.complete(Future.error('error')); + }); + test('fires valueOrCancellation with the given value', () { var completer = CancelableCompleter(); expect(completer.operation.valueOrCancellation(1), completion(equals(1)));
diff --git a/test/result/result_future_test.dart b/test/result/result_future_test.dart index 7171005..d211dca 100644 --- a/test/result/result_future_test.dart +++ b/test/result/result_future_test.dart
@@ -35,7 +35,7 @@ // The completer calls its listeners asynchronously. We have to wait // before we can access the result. - return future.catchError((_) {}).then((_) { + return future.onError((_, __) {}).then((_) { var error = future.result!.asError!; expect(error.error, equals('error')); expect(error.stackTrace, equals(trace));
diff --git a/test/stream_sink_transformer_test.dart b/test/stream_sink_transformer_test.dart index e5f6baa..20dda61 100644 --- a/test/stream_sink_transformer_test.dart +++ b/test/stream_sink_transformer_test.dart
@@ -80,9 +80,9 @@ // The futures shouldn't complete until the inner sink's close future // completes. var doneResult = ResultFuture(sink.done); - doneResult.catchError((_) {}); + doneResult.onError((_, __) {}); var closeResult = ResultFuture(sink.close()); - closeResult.catchError((_) {}); + closeResult.onError((_, __) {}); await flushMicrotasks(); expect(doneResult.isComplete, isFalse); expect(closeResult.isComplete, isFalse); @@ -177,9 +177,9 @@ // The futures shouldn't complete until the inner sink's close future // completes. var doneResult = ResultFuture(sink.done); - doneResult.catchError((_) {}); + doneResult.onError((_, __) {}); var closeResult = ResultFuture(sink.close()); - closeResult.catchError((_) {}); + closeResult.onError((_, __) {}); await flushMicrotasks(); expect(doneResult.isComplete, isFalse); expect(closeResult.isComplete, isFalse);