Skip to content

Future.value: consider completing the future immediately, without scheduling an extra microtask #49381

Open
@alexmarkov

Description

@alexmarkov

Documentation for Future.value says:

/// If [value] is not a [Future], the created future is completed
/// with the [value] value,

However, the implementation calls _Future<T>.immediate

factory Future.value([FutureOr<T>? value]) {
return new _Future<T>.immediate(value == null ? value as T : value);

It schedules a microtask to complete the future:

_Future.immediate(FutureOr<T> result) : _zone = Zone._current {
_asyncComplete(result);

void _asyncComplete(FutureOr<T> value) {
assert(!_isComplete);
// Two corner cases if the value is a future:
// 1. the future is already completed and an error.
// 2. the future is not yet completed but might become an error.
// The first case means that we must not immediately complete the Future,
// as our code would immediately start propagating the error without
// giving the time to install error-handlers.
// However the second case requires us to deal with the value immediately.
// Otherwise the value could complete with an error and report an
// unhandled error, even though we know we are already going to listen to
// it.
if (value is Future<T>) {
_chainFuture(value);
return;
}
// TODO(40014): Remove cast when type promotion works.
// This would normally be `as T` but we use `as dynamic` to make the
// unneeded check be implicit to match dart2js unsound optimizations in the
// user code.
_asyncCompleteWithValue(value as dynamic); // Value promoted to T.
}

void _asyncCompleteWithValue(T value) {
_setPendingComplete();
_zone.scheduleMicrotask(() {
_completeWithValue(value);
});
}

Scheduling a microtask is slower than just completing future synchronously. It also doesn't fully follow the documented behavior (the created Future is not fully completed until the microtask runs).

A good example of users struggling with this behavior is a SynchronousFuture from Flutter:

/// A [Future] whose [then] implementation calls the callback immediately.
///
/// This is similar to [Future.value], except that the value is available in
/// the same event-loop iteration.
///
/// ⚠ This class is useful in cases where you want to expose a single API, where
/// you normally want to have everything execute synchronously, but where on
/// rare occasions you want the ability to switch to an asynchronous model. **In
/// general use of this class should be avoided as it is very difficult to debug
/// such bimodal behavior.**
class SynchronousFuture<T> implements Future<T> {

https://github.com/flutter/flutter/blob/1e14993c56f668afcd2175c7ae847599a8ec11ee/packages/flutter/lib/src/foundation/synchronous_future.dart#L7-L17

It looks like SynchronousFuture would not be needed if Future.value would complete the Future synchronously (and maybe Future.then would also avoid scheduling an extra microtask for the completed future).

In addition to Future.value, it would be nice to revise other places where the current Future implementation schedules extra unnecessary microtasks when value is already available / future is already completed.

@lrhn @mkustermann @mraleph @Hixie

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-core-librarySDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries.library-async

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions