1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-03 00:08:46 +00:00

Add unawaited function and ignore extensions member.

The `unawaited` function in `dart:async` is intended for use with the `unawaited_futures` lint which is hopefully going to be part of the Dart recommended set of lints.

The `ignore` extension method is there to provide an alternative if you even want to ignore errors from a future. By having both, it makes the distinction clearer and makes it easier to not think one can be used for everything.

Change-Id: Ib96ed5ff64ead4b228721e5210efa82f76119c9f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/200428
Reviewed-by: Jacob Richman <jacobr@google.com>
Reviewed-by: Nate Bosch <nbosch@google.com>
Commit-Queue: Lasse R.H. Nielsen <lrn@google.com>
This commit is contained in:
Lasse R.H. Nielsen 2021-06-22 20:03:13 +00:00 committed by commit-bot@chromium.org
parent 8aec705f69
commit 902f149e2c
11 changed files with 457 additions and 25 deletions

View File

@ -12,9 +12,14 @@
#### `dart:async`
* The uncaught error handlers of `Zone`s are now run in the parent zone
of the zone where they were declared. This prevents a throwing handler
from causing an infinite loop by repeatedly triggering itself.
* The uncaught error handlers of `Zone`s are now run in the parent zone
of the zone where they were declared. This prevents a throwing handler
from causing an infinite loop by repeatedly triggering itself.
* Added `ignore()` as extension member on futures.
* Added `void unawaited(Future)` top-level function to deal with
the `unawaited_futures` lint.
#### `dart:core`

View File

@ -698,6 +698,30 @@ abstract class Future<T> {
Future<T> timeout(Duration timeLimit, {FutureOr<T> onTimeout()?});
}
/// Explicitly ignores a future.
///
/// Not all futures need to be awaited.
/// The Dart linter has an optional ["unawaited futures" lint](https://dart-lang.github.io/linter/lints/unawaited_futures.html)
/// which enforces that futures (expressions with a static type of [Future])
/// in asynchronous functions are handled *somehow*.
/// If a particular future value doesn't need to be awaited,
/// you can call `unawaited(...)` with it, which will avoid the lint,
/// simply because the expression no longer has type [Future].
/// Using `unawaited` has no other effect.
/// You should use `unawaited` to convey the *intention* of
/// deliberately not waiting for the future.
///
/// If the future completes with an error,
/// it was likely a mistake to not await it.
/// That error will still occur and will be considered unhandled
/// unless the same future is awaited (or otherwise handled) elsewhere too.
/// Because of that, `unawaited` should only be used for futures that
/// are *expected* to complete with a value.
/// You can use [FutureExtension.ignore] if you also don't want to know
/// about errors from this future.
@Since("2.15")
void unawaited(Future<void> future) {}
/// Convenience methods on futures.
///
/// Adds functionality to futures which makes it easier to
@ -770,6 +794,32 @@ extension FutureExtensions<T> on Future<T> {
handleError(error as E, stackTrace),
test: (Object error) => error is E && (test == null || test(error)));
}
/// Completely ignores this future and its result.
///
/// Not all futures are important, not even if they contain errors,
/// for example if a request was made, but the response is no longer needed.
/// Simply ignoring a future can result in uncaught asynchronous errors.
/// This method instead handles (and ignores) any values or errors
/// coming from this future, making it safe to otherwise ignore
/// the future.
///
/// Use `ignore` to signal that the result of the future is
/// no longer important to the program, not even if it's an error.
/// If you merely want to silence the ["unawaited futures" lint](https://dart-lang.github.io/linter/lints/unawaited_futures.html),
/// use the [unawaited] function instead.
/// That will ensure that an unexpected error is still reported.
@Since("2.15")
void ignore() {
var self = this;
if (self is _Future<T>) {
self._ignore();
} else {
self.then<void>(_ignore, onError: _ignore);
}
}
static void _ignore(Object? _, [Object? __]) {}
}
/// Thrown when a scheduled timeout happens while waiting for an async result.

View File

@ -69,9 +69,14 @@ class _FutureListener<S, T> {
static const int maskTestError = 4;
static const int maskWhenComplete = 8;
static const int stateChain = 0;
// Handles values, passes errors on.
static const int stateThen = maskValue;
// Handles values and errors.
static const int stateThenOnerror = maskValue | maskError;
// Handles errors, has errorCallback.
static const int stateCatchError = maskError;
// Ignores both values and errors. Has no callback or errorCallback.
// The [result] future is ignored, its always the same as the source.
static const int stateCatchErrorTest = maskError | maskTestError;
static const int stateWhenComplete = maskWhenComplete;
static const int maskType =
@ -191,21 +196,40 @@ class _Future<T> implements Future<T> {
/// [_FutureListener] listeners.
static const int _stateIncomplete = 0;
/// Flag set when an error need not be handled.
///
/// Set by the [FutureExtensions.ignore] method to avoid
/// having to introduce an unnecessary listener.
/// Only relevant until the future is completed.
static const int _stateIgnoreError = 1;
/// Pending completion. Set when completed using [_asyncComplete] or
/// [_asyncCompleteError]. It is an error to try to complete it again.
/// [_resultOrListeners] holds listeners.
static const int _statePendingComplete = 1;
static const int _statePendingComplete = 2;
/// The future has been chained to another future. The result of that
/// other future becomes the result of this future as well.
/// The future has been chained to another "source" [_Future].
///
/// The result of that other future becomes the result of this future
/// as well, when the other future completes.
/// This future cannot be completed again.
/// [_resultOrListeners] contains the source future.
static const int _stateChained = 2;
/// Listeners have been moved to the chained future.
static const int _stateChained = 4;
/// The future has been completed with a value result.
static const int _stateValue = 4;
///
/// [_resultOrListeners] contains the value.
static const int _stateValue = 8;
/// The future has been completed with an error result.
static const int _stateError = 8;
///
/// [_resultOrListeners] contains an [AsyncEror]
/// holding the error and stack trace.
static const int _stateError = 16;
/// Mask for the states above except [_stateIgnoreError].
static const int _completionStateMask = 30;
/// Whether the future is complete, and as what.
int _state = _stateIncomplete;
@ -227,8 +251,8 @@ class _Future<T> implements Future<T> {
/// and it is not chained to another future.
///
/// The future is another future that this future is chained to. This future
/// is waiting for the other future to complete, and when it does, this future
/// will complete with the same result.
/// is waiting for the other future to complete, and when it does,
/// this future will complete with the same result.
/// All listeners are forwarded to the other future.
@pragma("vm:entry-point")
var _resultOrListeners;
@ -253,12 +277,14 @@ class _Future<T> implements Future<T> {
/// Creates a future that is already completed with the value.
_Future.value(T value) : this.zoneValue(value, Zone._current);
bool get _mayComplete => _state == _stateIncomplete;
bool get _isPendingComplete => _state == _statePendingComplete;
bool get _mayAddListener => _state <= _statePendingComplete;
bool get _isChained => _state == _stateChained;
bool get _isComplete => _state >= _stateValue;
bool get _hasError => _state == _stateError;
bool get _mayComplete => (_state & _completionStateMask) == _stateIncomplete;
bool get _isPendingComplete => (_state & _statePendingComplete) != 0;
bool get _mayAddListener =>
_state <= (_statePendingComplete | _stateIgnoreError);
bool get _isChained => (_state & _stateChained) != 0;
bool get _isComplete => (_state & (_stateValue | _stateError)) != 0;
bool get _hasError => (_state & _stateError) != 0;
bool get _ignoreError => (_state & _stateIgnoreError) != 0;
static List<Function>? _continuationFunctions(_Future<Object> future) {
List<Function>? result = null;
@ -283,7 +309,7 @@ class _Future<T> implements Future<T> {
void _setChained(_Future source) {
assert(_mayAddListener);
_state = _stateChained;
_state = _stateChained | (_state & _stateIgnoreError);
_resultOrListeners = source;
}
@ -315,6 +341,10 @@ class _Future<T> implements Future<T> {
return result;
}
void _ignore() {
_state |= _stateIgnoreError;
}
Future<T> catchError(Function onError, {bool test(Object error)?}) {
_Future<T> result = new _Future<T>();
if (!identical(result._zone, _rootZone)) {
@ -337,13 +367,13 @@ class _Future<T> implements Future<T> {
Stream<T> asStream() => new Stream<T>.fromFuture(this);
void _setPendingComplete() {
assert(_mayComplete);
_state = _statePendingComplete;
assert(_mayComplete); // Aka _statIncomplete
_state ^= _stateIncomplete ^ _statePendingComplete;
}
void _clearPendingComplete() {
assert(_isPendingComplete);
_state = _stateIncomplete;
_state ^= _statePendingComplete ^ _stateIncomplete;
}
AsyncError get _error {
@ -365,7 +395,7 @@ class _Future<T> implements Future<T> {
void _setErrorObject(AsyncError error) {
assert(!_isComplete); // But may have a completion pending.
_state = _stateError;
_state = _stateError | (_state & _stateIgnoreError);
_resultOrListeners = error;
}
@ -379,7 +409,8 @@ class _Future<T> implements Future<T> {
void _cloneResult(_Future source) {
assert(!_isComplete);
assert(source._isComplete);
_state = source._state;
_state =
(source._state & _completionStateMask) | (_state & _stateIgnoreError);
_resultOrListeners = source._resultOrListeners;
}
@ -615,7 +646,7 @@ class _Future<T> implements Future<T> {
assert(source._isComplete);
bool hasError = source._hasError;
if (listeners == null) {
if (hasError) {
if (hasError && !source._ignoreError) {
AsyncError asyncError = source._error;
source._zone
.handleUncaughtError(asyncError.error, asyncError.stackTrace);

View File

@ -2,7 +2,10 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Ensures a context type of [T] for the operand.
/// Helper to create [Type] values.
Type typeOf<T>() => T;
/// Ensures a context type of [T] for the operand.
void context<T>(T x) {}
/// Captures the context type of the call and returns the same type.
@ -34,6 +37,23 @@ extension StaticType<T> on T {
T expectStaticType<R extends Exactly<T>>() {
return this;
}
/// Invokes [callback] with the static type of `this`.
///
/// Allows any operation on the type.
T captureStaticType(void Function<X>() callback) {
callback<T>();
return this;
}
}
/// Invokes [callback] with the static type of [value].
///
/// Similar to [StaticType.captureStaticType], but works
/// for types like `void` and `dynamic` which do not allow
/// extension methods.
void captureStaticType<T>(T value, void Function<X>(X value) callback) {
callback<T>(value);
}
/// Use with [StaticType.expectStaticType] to expect precisely the type [T].

View File

@ -0,0 +1,90 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Helper to create [Type] values.
Type typeOf<T>() => T;
/// Ensures a context type of [T] for the operand.
void context<T>(T x) {}
/// Captures the context type of the call and returns the same type.
///
/// Can be used to check the context type as:
/// ```dart
/// int x = contextType(1 /* valid value */)..expectStaticType<Exactly<int>>;
/// ```
T contextType<T>(Object result) => result as T;
extension StaticType<T> on T {
/// Check the static type.
///
/// Use as follows (assuming `e` has static type `num`):
/// ```dart
/// e.expectStaticType<Exactly<num>>() // No context type.
/// e.expectStaticType<SubtypeOf<Object>>() // No context type.
/// e.expectStaticType<SupertypeOf<int>>() // No context type.
/// ```
/// or
/// ```dart
/// e..expectStaticType<Exactly<num>>() // Preserve context type.
/// e..expectStaticType<SubtypeOf<Object>>() // Preserve context type.
/// e..expectStaticType<SupertypeOf<int>>() // Preserve context type.
/// ```
/// This will be a *compile-time error* if the static type is not
/// as required by the constraints type (the one passed to [Exactly],
/// [SubtypeOf] or [SupertypeOf].)
T expectStaticType<R extends Exactly<T>>() {
return this;
}
/// Invokes [callback] with the static type of `this`.
///
/// Allows any operation on the type.
T captureStaticType(void Function<X>() callback) {
callback<T>();
return this;
}
}
/// Invokes [callback] with the static type of [value].
///
/// Similar to [StaticType.captureStaticType], but works
/// for types like `void` and `dynamic` which do not allow
/// extension methods.
void captureStaticType<T>(T value, void Function<X>(X value) callback) {
callback<T>(value);
}
/// Use with [StaticType.expectStaticType] to expect precisely the type [T].
///
/// Example use:
/// ```dart
/// "abc".expectStaticType<Exactly<String>>();
/// ```
typedef Exactly<T> = T Function(T);
/// Use with [StaticType.expectStaticType] to expect a subtype of [T].
///
/// Example use:
/// ```dart
/// num x = 1;
/// x.expectStaticType<SubtypeOf<Object>>();
/// ```
typedef SubtypeOf<T> = Never Function(T);
/// Use with [StaticType.expectStaticType] to expect a supertype of [T].
///
/// Example use:
/// ```dart
/// num x = 1;
/// x.expectStaticType<SupertypeOf<int>>();
/// ```
typedef SupertypeOf<T> = T Function(Object?);
/// Checks that an expression is assignable to [T1], [T2] and [Object].
///
/// This ensures that the static type of the expression is either dynamic,
/// Never, or a type assignable to both [T1] and [T2], and if those are
/// unrelated, it must be an intersection type.
void checkIntersectionType<T1, T2>(T1 v1, T2 v2, Object v3) {}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
import 'dart:async' show Completer, runZonedGuarded;
import '../../language/static_type_helper.dart';
void main() {
testIgnore();
}
void testIgnore() {
var future = Future<int>.value(42);
captureStaticType(future.ignore(), <T>(T value) {
Expect.equals(typeOf<void>(), T);
});
asyncStart();
// Ignored futures can still be listend to.
{
var c = Completer<int>.sync();
var f = c.future;
f.ignore();
asyncStart();
f.catchError((e) {
Expect.equals("ERROR1", e);
asyncEnd();
return 0;
});
c.completeError("ERROR1");
}
// Ignored futures are not uncaught errors.
{
asyncStart();
bool threw = false;
runZonedGuarded(() {
var c = Completer<int>.sync();
var f = c.future;
f.ignore();
c.completeError("ERROR2");
}, (e, s) {
threw = true;
Expect.fail("Should not happen: $e");
});
Future.delayed(Duration.zero, () {
if (threw) Expect.fail("Future not ignored.");
asyncEnd();
});
}
asyncEnd();
}

View File

@ -0,0 +1,11 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
void main() {
// The `unawaited` function is not exposed by dart:core.
unawaited;
// [error line 7, column 3, length 9]
// [cfe] Getter not found: 'unawaited'.
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_IDENTIFIER
}

View File

@ -0,0 +1,52 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
import 'dart:async' show Completer, runZonedGuarded, unawaited;
import 'dart:async' as prefix;
import '../../language/static_type_helper.dart';
void main() {
testUnawaited();
}
void testUnawaited() {
// Exists where expected.
prefix.unawaited.expectStaticType<Exactly<void Function(Future<Object?>)>>();
var future = Future<int>.value(42);
captureStaticType(unawaited(future), <T>(value) {
Expect.equals(typeOf<void>(), T);
});
asyncStart();
// Unawaited futures still throw.
{
var c = Completer<int>();
var f = c.future;
unawaited(f);
asyncStart();
f.catchError((e) {
Expect.equals("ERROR1", e);
asyncEnd();
return 0;
});
c.completeError("ERROR1");
}
// Unawaited futures are still uncaught errors.
{
asyncStart();
runZonedGuarded(() {
var c = Completer<int>();
var f = c.future;
unawaited(f);
c.completeError("ERROR2");
}, (e, s) {
Expect.equals("ERROR2", e);
asyncEnd();
});
}
asyncEnd();
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
import 'dart:async' show Completer, runZonedGuarded;
import '../../language/static_type_helper.dart';
void main() {
testIgnore();
}
void testIgnore() {
var future = Future<int>.value(42);
captureStaticType(future.ignore(), <T>(T value) {
Expect.equals(typeOf<void>(), T);
});
asyncStart();
// Ignored futures can still be listend to.
{
var c = Completer<int>.sync();
var f = c.future;
f.ignore();
asyncStart();
f.catchError((e) {
Expect.equals("ERROR1", e);
asyncEnd();
return 0;
});
c.completeError("ERROR1");
}
// Ignored futures are not uncaught errors.
{
asyncStart();
bool threw = false;
runZonedGuarded(() {
var c = Completer<int>.sync();
var f = c.future;
f.ignore();
c.completeError("ERROR2");
}, (e, s) {
threw = true;
Expect.fail("Should not happen: $e");
});
Future.delayed(Duration.zero, () {
if (threw) Expect.fail("Future not ignored.");
asyncEnd();
});
}
asyncEnd();
}

View File

@ -0,0 +1,11 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
void main() {
// The `unawaited` function is not exposed by dart:core.
unawaited;
// [error line 7, column 3, length 9]
// [cfe] Getter not found: 'unawaited'.
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_IDENTIFIER
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
import 'package:async_helper/async_helper.dart';
import "package:expect/expect.dart";
import 'dart:async' show Completer, runZonedGuarded, unawaited;
import 'dart:async' as prefix;
import '../../language/static_type_helper.dart';
void main() {
testUnawaited();
}
void testUnawaited() {
// Exists where expected.
prefix.unawaited.expectStaticType<Exactly<void Function(Future<Object>)>>();
var future = Future<int>.value(42);
captureStaticType(unawaited(future), <T>(value) {
Expect.equals(typeOf<void>(), T);
});
asyncStart();
// Unawaited futures still throw.
{
var c = Completer<int>();
var f = c.future;
unawaited(f);
asyncStart();
f.catchError((e) {
Expect.equals("ERROR1", e);
asyncEnd();
return 0;
});
c.completeError("ERROR1");
}
// Unawaited futures are still uncaught errors.
{
asyncStart();
runZonedGuarded(() {
var c = Completer<int>();
var f = c.future;
unawaited(f);
c.completeError("ERROR2");
}, (e, s) {
Expect.equals("ERROR2", e);
asyncEnd();
});
}
asyncEnd();
}