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