The Event Loop and Futures
24.1 The Event Loop
Each isolate runs a single-threaded event loop with two queues:
The microtask queue (high priority: scheduleMicrotask, completed Future callbacks)
The event queue (I/O, timers, user events, messages from other isolates)
Code runs to completion. Asynchronous operations schedule callbacks rather than blocking.
void main() {
print('1 sync');
Future(() => print('4 event queue'));
scheduleMicrotask(() => print('3 microtask'));
Future.value(1).then((_) => print('3b microtask (completed future)'));
print('2 sync');
}
// Output: 1 sync, 2 sync, 3 microtask, 3b..., 4 event queue24.2 Futures
A Future<T> represents a value (or error) that will be available later. Its states are uncompleted, completed with a value, and completed with an error.
Future<String> fetchUser() => Future.delayed(const Duration(seconds: 1), () => 'Ana');
void main() {
fetchUser()
.then((user) => print('User: $user'))
.catchError((e) => print('Error: $e'))
.whenComplete(() => print('Done'));
final immediate = Future.value(42);
final failed = Future<int>.error(StateError('boom'));
final sync = Future.sync(() => compute());
final delayed = Future.delayed(const Duration(milliseconds: 500));
}24.3 Combining Futures
final results = await Future.wait([fetchA(), fetchB(), fetchC()]); // all (fails fast on the first error)
final (user, posts) = await (fetchUser(), fetchPosts()).wait; // records of futures (Dart 3)
final fastest = await Future.any([mirror1(), mirror2()]); // first to complete
final value = await fetchUser().timeout(const Duration(seconds: 5), onTimeout: () => 'fallback');
await Future.forEach([1, 2, 3], (n) async => await process(n)); // sequential
final results2 = await Future.wait(urls.map(download), eagerError: true);24.4 Completer
import 'dart:async';
Future<String> legacyToFuture() {
final completer = Completer<String>();
legacyApi(onSuccess: completer.complete, onError: completer.completeError);
return completer.future;
}