Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of `FutureOr`?

Tags:

dart

Dart offers a FutureOr class, that allows writing:

FutureOr<int> future;  future = 42; // valid future = Future.value(42); // also valid 

I would assume that FutureOr would be useful to remove the unnecessary delay caused by the event loop if the value can be read synchronously.

But that doesn't seem to be the case, as showcased by:

import 'dart:async';  void main() async {   print('START');   futureOrExample();   print('END'); }  void futureOrExample() async {   FutureOr<int> futureOr = 42;   print('before await');   await futureOr;   print('end await'); } 

which prints:

START before await END end await 

when I would expect:

START before await end await END 

In that case, why does FutureOr (or more generally await 42) work this way?

Similarly, what's the purpose of FutureOr in that situation since it produces the same result as Future?

I know that I could use SynchronousFuture to achieve the desired result, but I'm just trying to understand what's the use of FutureOr.

like image 371
Rémi Rousselet Avatar asked Dec 06 '19 12:12

Rémi Rousselet


1 Answers

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.

like image 105
lrn Avatar answered Oct 05 '22 02:10

lrn