What is the purpose of `FutureOr`?

The use of FutureOr, as introduced with Dart 2, is to allow you to provide either a value or a future at a point where the existing Dart 1 API allowed the same thing for convenience, only in a way that can be statically typed.

The canonical example is Future.then. The signature on Future<T> is Future<R> then<R>(FutureOr<R> action(T value), {Function onError}).

The idea is that you can have an action on the future's value which is either synchronous or asynchronous. Originally there was a then function which took a synchronous callback and a chain function which took an asynchronous callback, but that was highly annoying to work with, and in good Dart 1 style, the API was reduced to one then method which took a function returning dynamic, and then it checked whether it was a future or not.

In Dart 1 it was easy to allow you to return either a value or a future. Dart 2 was not as lenient, so the FutureOr type was introduced to allow the existing API to keep working. If we had written the API from scratch, we'd probably have done something else, but migrating the existing asynchronous code base to something completely different was not an option, so the FutureOr type was introduced as a type-level hack.

The await operation was also originally defined to work on any object, long before FutureOr existed. For consistency and smaller code, an await e where e evaluated to a non-future would wrap that value in a future and await that. It means that there is only one quick and reusable check on a value (is it a future, if not wrap it), and then the remaining code is the same. There is only one code-path. If the await worked synchronously on non-Future values, there would have to be a synchronous code path running through the await, as well as an asynchronous path waiting for a future. That would potentially double the code size, for example when compiling to JavaScript (or worse, if there were more awaits in the same control flow, you could get exponential blow-up for a naive implementation). Even if you avoided that by just calling the continuation function synchronously, it would likely be confusing to some readers that an await would not introduce an asynchronous gap. A mistake around that can cause race conditions or things happening in the wrong order.

So, the original design, predating FutureOr, was to make all await operations actually wait.

The introduction of FutureOr did not change this reasoning, and even if it did, it would now be a breaking change to not wait in places where people expect their code to actually give time for other microtasks to run.


The await keyword always lock the function execution.

Writing:

await 42

Is equivalent to:

await Future.value(42)

The reason being:

  • This is how await works in Javascript
  • it makes the behavior of await consistent.

Now, what's the purpose of FutureOr then?

FutureOr was never intended as a way to potentially make await synchronous.

Instead, it is an implementation detail of Future.

Without FutureOr, writing the following would not compile:

Future(() {
  return 42; // compile error, not a Future
});

Future<int> future;
future.then((value) {
  return value * 2; // compile error, not a Future
});

Instead, we would have to wrap all values in a Future.value like so:

Future<int> future;
future.then((value) {
  return Future.value(value * 2);
});

Tags:

Dart