Future vs Future.sync vs Future.microtask vs Future.value, etc — Flutter Experiments

Explaining with samples about Future constructors in Flutter.

Matatias Situmorang
7 min readApr 26, 2023
Photo by Joshua Rawson-Harris on Unsplash

A few days ago, I just learned better ways to Initialize a provider in initState(). In the documentation, it says an option to use Future.microtask.Been studying Flutter for 2 years, and I have never used this constructor in my code. So, I’m a bit curious about the other constructors in the Future class. And now in this article, I want to share my understanding of them. For anyone with Flutter experience, feel free to clarify and comment below if I explained something wrong. I really appreciate it 🙏.

Future is the result of an asynchronous computation. When we have this computation in Flutter, we may need to wait for something external which takes time. And after its complete, we can receive the result of the computation.

If we take a look at the documentation, the Future class in Flutter has 6 constructors:

  • Future: Creates a future containing the result of calling computation asynchronously with Timer.run.
  • Future.delayed: Creates a future that runs its computation after a delay.
  • Future.error: Creates a future that completes with an error.
  • Future.microtask: Creates a future containing the result of calling computation asynchronously with scheduleMicrotask.
  • Future.sync: Returns a future containing the result of immediately calling computation.
  • Future.value: Creates a future completed with value.

The first thing I want to know is the difference between them. From the description, all these constructors are creating a future. The easiest to understand is Future.delayed which executes the computation after the delay. So, I can assume that it is the same as the normal Future constructor but with an extra customizable delay. Another one is Future.error. This constructor will complete the computation with the error value. That makes it different from the others.

For the other 4 constructors let’s compare with the example below:

Cycle

This example I found this from someone’s post on Reddit. Thanks to W_C_K_D for an awesome experiment to break down the cycle of the Future class. Let's see the code below:

import 'dart:async';

void main(List<String> arguments) {
print('Start');

Future(() => 1).then(print);
Future(() => Future(() => 2)).then(print);

Future.value(3).then(print);
Future.value(Future(() => 4)).then(print);

Future.sync(() => 5).then(print);
Future.sync(() => Future(() => 6)).then(print);

Future.microtask(() => 7).then(print);
Future.microtask(() => Future(() => 8)).then(print);

Future(() => 9).then(print);
Future(() => Future(() => 10)).then(print);

print('End');
}

result:

Start                                                
End
3
5
7
1
4
6
9
8
2
10

To break down this cycle we divide it into 3 sequences. Event queue, Microtask queue, and print. In the start, we have empty for each sequence:

Event queue:
Microtask queue:
Print:

If we take a look at the code above, we have Future and some of them are nested future. Now for the first execution, from print(Start) until print (End)we will have a sequence like below:

Event queue: F(10), 9, 6, 4, F(2), 1
Microtask queue: F(8), 7, 5, 3
Print: Start End

note: F (10) = Future(()=> 10)

We can imagine that we have a FIFO structure.

  • Start, and End was on the print sequence. It is because from the example we immediately print it without asynchronous computation.
  • in the microtask sequence we have 3, 5, 7, and F (8).
    7 and F (8) is clear enough: because we use Future.microtask, that's why we put it into Microtask queue sequence.
Future.microtask(() => 7).then(print);
Future.microtask(() => Future(() => 8)).then(print);

Future.value(x) and Future.sync(x) has similar [but no same] behavior:

  • x is not future => x will be set as a new microtask event into the microtask queue
  • x is Future => x will be set as a new event into the event queue

that's why from the code we have

Future.value(3).then(print);
Future.value(Future(() => 4)).then(print);
Future.sync(() => 5).then(print);
Future.sync(() => Future(() => 6)).then(print);

3 and 5 will be set to microtask queue and F (4) and F (6) will be set as a new events into event queue.

The normal Future that we have in 1, 2, 9, and 10 will be set into event queue.

In the Flutter Future class, microtask has priority. Thats why the microtask queue will process first before the event queue.

Here is what we have after executing all microtask queue.

Event queue: 8, F(10), 9, 6, 4, F(2), 1
Microtask queue:
Print: Start End 3 5 7

The microtask sequence is now empty. 3, 5, and 7 are not asynchronous, that why they will immediately print. We place it in the Print sequence. And for the F (8) is a normal Future. It will create new events and place to the Event queue.

And for the last cycle is executing the Event queue. Since It still contained F (2) and F (10), it will need 2 steps.

first: Print all non-future value and create a new event for Future value.

Event queue: 10 2
Microtask queue:
Print: Start End 3 5 7 1 4 6 9 8

and last step: print all remaining events.

Event queue:
Microtask queue:
Print: Start End 3 5 7 1 4 6 9 8 2 10

And that's why we have printed values like this sequence:

Start End 3 5 7 1 4 6 9 8 2 10

If you want to read a detailed line-by-line explanation, I highly recommend reading the source post on Reddit.

Photo by Belinda Fewings on Unsplash

Future.value vs Future.sync

As I said before, Future.value and Future.sync have similar behavior. But they are also different. I found a few issues on GitHub, and this is one of the simplest to compare.

source: Future.value breaks a promise of the async docs · Issue #52006 · dart-lang/sdk (github.com)

We will compare it with scheduleMicrotask.

  • Future.sync()
import 'dart:async';
void main() {
final future = Future.sync(() => 2);
//Non-future returned in computation, therefore future is completed.

scheduleMicrotask(() {
print(1);
}); //we invoke scheduleMicrotask before attaching listeners to future.

future.then(print);
}

expected result:

1
2

actual result:

1
2

now we use the same code but change it with Future.value

  • Future.value()
import 'dart:async';
void main() {
final future = Future.value(2);
//value is not a future and therefore the created future should be completed with the value 'value' equivalently to Future.sync.

scheduleMicrotask(() {
print(1);
}); //we invoke scheduleMicrotask before any listeners.

future.then(print);
}

expected result:

1
2

actual result:

2
1

from the example above, we can see the difference between them.

Implementations

Now let's see how and when we can use this constructor in a real Flutter App.

  1. Future<T>

This is common and we may use this often. In my case, I usually use this constructor for HTTP requests. for example:

Future<MyModel> fetchMyData() async{
try{
final response = await http.get(Uri.parse("url here"));
if(response.statusCode ==200){
return MyModel.fromJson(jsonDecode(resp.body);
}else{
throw("");
}
}catch(e){
rethrow;
}
}

2. Future<T>.microTask()

This future is a priority execution compared to the normal Future. An example implementation found on initiating provider state management.

initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>().fetchSomething(someValue);
);
}

3. Future<T>.sync()

This constructor is rarely implemented on the Flutter app. After searching on the internet, I end up with flutter documentation about the future and error handling.

Usually, to use future methods, we always use try{}catch{} methods. And this always works like a charm. But then after reading the documentation there is some case that the future function is mixing synchronous and asynchronous errors. For example, when we use the method from dart.io like obtainFileName() and parseFileData(). These 2 methods could potentially throw synchronously. Because the method executes inside then() method. Using catchError() does not capture the error. That's why we need to handle it with Future.sync.

Honestly, I'm not successful to reproduce this issue. So, I will use examples from the documentation in Flutter docs.

example case:

Future<int> parseAndRead(Map<String, dynamic> data) {
return Future.sync(() {
final filename = obtainFilename(data); // Could throw.
final file = File(filename);
return file.readAsString().then((contents) {
return parseFileData(contents); // Could throw.
});
});
}

If the callback returns a non-Future value, Future.sync()’s Future completes with that value. If the callback throws (as it does in the example above), the Future completes with an error. If the callback itself returns a Future, the value or the error of that Future completes Future.sync()’s Future.

now we can use it like:

void main() {
parseAndRead(data).catchError((e) {
print('Inside catchError');
print(e);
return -1;
});
}

Future.sync() makes your code resilient against uncaught exceptions.

4. Future<T>.value()

This constructor creates a future completed with a value.

Future<int> getFuture() {
return Future<int>.value(2021);
}

final result = await getFuture();

5. Future<T>.delayed()

We can set the duration for the delay to the computation in the future. It will execute after the duration that we set.

Future.delayed(const Duration(seconds: 1), () {
print('One second has passed.'); // Prints after 1 second.
});

6. Future<T>.error()

This constructor creates a future that will be completed with an error in a future microtask.

Future<int> getFuture() {
return Future.error(Exception('Issue'));
}

final error = await getFuture(); // Throws.

Conclusion

  • Future.delayed = delay + normal Future
  • Future.value(x) behavior:
    x is not future => x will be set as a new microtask event into the microtask queue
    x is Future => x will be set as a new event into the event queue
  • Future.value != Future.sync
  • Microtask is a priority in future
  • Future.sync() makes your code resilient against uncaught exceptions.

Thank you for reading 🙏. Don't forget to clap 👏 if you like this article.

References

--

--

Matatias Situmorang

Flutter developer | Blogger | Reach me on twitter @pmatatias_