Skip to content

Async and Futures

Dart runs on a single-threaded event loop with an isolated memory model. Unlike languages with Threads (Java, C++, Rust), Dart uses event-driven concurrency — the single thread processes Events from a queue, interleaving async operations without blocking.

This design is fundamental to Flutter”s architecture: the UI must remain responsive (60 fps) while Performing I/O (network requests, file reads, database queries). If any operation blocks the thread, The entire UI freezes.

flowchart TD
    A["Event Loop"] --> B{"Is the queue empty?"}
    B -->|No| C["Dequeue next event"]
    C --> D["Execute event handler"]
    D --> E{"Handler complete?"}
    E -->|Yes| B
    E -->|No<br/>(async result pending)| F["Register callback<br/>in microtask queue"]
    F --> G["Yield to event loop"]
    G --> B
    B -->|Yes| H["Idle<br/>(wait for next I/O event)"]
    H --> B

A Future<T> represents a value that will be available at some point in the future — either a value Of type T or an error. It is Dart’s equivalent of JavaScript’s Promise or Rust’s Future.

// From a computation (runs on the event loop when awaited)
Future<int> computeSquare(int n) async {
return n * n;
}
// From a callback-based API
Future<http.Response> fetchUser() {
return http.get(Uri.parse('https://api.example.com/user'));
}
// With Future.value (immediately resolved)
Future<String> cachedGreeting() {
return Future.value('Hello');
}
// With Future.delayed (resolved after a delay)
Future<void> delayedGreeting() async {
await Future.delayed(Duration(seconds: 1));
print('Hello after 1 second');
}
// With Future.error (immediately rejected)
Future<void> fail() {
return Future.error(Exception('Something went wrong'));
}

The async/await syntax is syntactic sugar for working with Futures. async marks a function as Asynchronous, and await suspends execution until the Future completes:

// Without async/await (callback style)
Future<void> loadData() {
return http.get(Uri.parse('https://api.example.com/data')).then((response) {
var data = jsonDecode(response.body);
print('Got ${data['items'].length} items');
}).catchError((error) {
print('Error: $error');
});
}
// With async/await (linear style — easier to read and reason about)
Future<void> loadData() async {
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
final data = jsonDecode(response.body);
print('Got ${data['items'].length} items');
} catch (error) {
print('Error: $error');
}
}
Future<int> fetchAge() async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
if (response.statusCode != 200) {
throw Exception('Failed to fetch user');
}
return jsonDecode(response.body)['age'] as int;
}
// Try-catch (preferred)
Future<void> example() async {
try {
final age = await fetchAge();
print('Age: $age');
} on FormatException catch (e) {
print('Invalid JSON: $e');
} on http.ClientException catch (e) {
print('Network error: $e');
} catch (e) {
print('Unexpected error: $e');
} finally {
print('Cleanup');
}
}
// catchError on the Future chain
Future<void> example() async {
final age = await fetchAge().catchError((e) {
print('Fallback: $e');
return 0;
});
}
// Sequential: each await blocks until the Future completes
Future<void> sequential() async {
final user = await fetchUser(); // 1 second
final orders = await fetchOrders(user.id); // 1 second
final profile = await fetchProfile(user.id); // 1 second
// Total: 3 seconds
}
// Parallel: all Futures start immediately, await all results
Future<void> parallel() async {
final results = await Future.wait([
fetchUser(),
fetchOrders('user-1'),
fetchProfile('user-1'),
]);
// Total: 1 second (all run concurrently)
final user = results[0];
final orders = results[1];
final profile = results[2];
}
// Parallel with named results
Future<void> parallelNamed() async {
final userFuture = fetchUser();
final ordersFuture = fetchOrders('user-1');
final profileFuture = fetchProfile('user-1');
// Each await blocks only until its own Future completes
final user = await userFuture;
final orders = await ordersFuture;
final profile = await profileFuture;
}