Dart: Dart Asynchronous Programming — Future / async-await
Last updated: 2026-08-26
Asynchronous programming lets your program not wait — a 3-second I/O operation lets the CPU do other work instead of just waiting.
1. What You'll Learn
- Future lifecycle: pending / completed / failed
- async / await syntactic sugar and error handling
- Future composition: Future.wait / Future.any / Future.forEach
- Completer: manually control Future completion
- Bob's scenario: DataPipeline making concurrent requests to multiple e-commerce APIs
2. A Developer's Real Story
(1) Pain Point: Sequential Requests Cause 12-Second Page Load
Bob's DataPipeline needs to fetch data from 3 APIs: orders (3 seconds), products (2 seconds), and customers (2 seconds). He initially used sequential requests: 3 + 2 + 2 = 7 seconds. Adding the database query (5 seconds), the entire report generation took 12 seconds. The user experience was terrible, and SaaS customers complained it was "too slow."
(2) Solution: Concurrent Execution with Future.wait
Using Future.wait makes the 3 API requests run concurrently, with the total time determined by the slowest one: 3 seconds. Combined with concurrent database queries, the entire report generation is shortened to 4 seconds.
// Sequential: 3 + 2 + 2 = 7 seconds
final orders = await fetchOrders(); // 3s
final products = await fetchProducts(); // 2s
final customers = await fetchCustomers(); // 2s
// Concurrent: max(3, 2, 2) = 3 seconds
final results = await Future.wait([
fetchOrders(),
fetchProducts(),
fetchCustomers(),
]);
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(3) Benefits
- Future.wait concurrent execution reduces API calls from 7 seconds to 3 seconds, overall 4 seconds.
- async/await makes asynchronous code look like synchronous code, improving readability.
- Robust error handling ensures network exceptions no longer crash the program.
3. Future Basics
(1) Future Lifecycle
sequenceDiagram participant Bob participant API1 participant API2 participant API3 Bob->>API1: fetchOrders() Bob->>API2: fetchProducts() Bob->>API3: fetchCustomers() Note over Bob: Future.wait concurrent API1-->>Bob: 1.2M orders API2-->>Bob: 500K products API3-->>Bob: 300K customers Bob->>Bob: merge & analyze
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Creating and Using a Future
import 'dart:async';
// Create a Future that completes after a delay
Future<String> fetchOrder() async {
await Future.delayed(const Duration(seconds: 2));
return 'ORD-001: \$1500.00 USD';
}
// Future with error
Future<String> fetchOrderWithRetry() {
return Future.delayed(const Duration(seconds: 1), () {
throw Exception('Network timeout');
});
}
void main() async {
// Pending → Completed
print('Fetching order...');
final order = await fetchOrder();
print(order);
// Pending → Failed
try {
await fetchOrderWithRetry();
} catch (e) {
print('Error: $e');
}
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
| Future State | Meaning |
|---|---|
| pending | Incomplete, waiting |
| completed with value | Completed successfully, carrying a value |
| completed with error | Completed with failure, carrying an exception |
4. async / await In-Depth
(1) Basic Syntax
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: async/await Basics
import 'dart:async';
Future<double> calculateOrderTotal(String orderId) async {
// await pauses execution until Future completes
final amount = await fetchAmount(orderId);
final taxRate = await fetchTaxRate(orderId);
return amount * (1 + taxRate);
}
Future<double> fetchAmount(String orderId) async {
await Future.delayed(const Duration(milliseconds: 500));
return 1500.0;
}
Future<double> fetchTaxRate(String orderId) async {
await Future.delayed(const Duration(milliseconds: 300));
return 0.08;
}
void main() async {
final total = await calculateOrderTotal('ORD-001');
print('Total: \$${total.toStringAsFixed(2)} USD');
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(2) Error Handling
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: async/await Error Handling
import 'dart:async';
Future<String> fetchApiData(String endpoint) async {
await Future.delayed(const Duration(seconds: 1));
if (endpoint.contains('invalid')) {
throw Exception('API error: $endpoint not found');
}
return 'Data from $endpoint';
}
Future<void> robustFetch(String endpoint) async {
try {
final data = await fetchApiData(endpoint);
print('Success: $data');
} on Exception catch (e) {
print('Exception: $e');
} finally {
print('Fetch completed for $endpoint');
}
}
void main() async {
await robustFetch('orders'); // Success
await robustFetch('invalid-endpoint'); // Exception
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
| Error Handling Method | Syntax | Use Case |
|---|---|---|
| try-catch | try { await f(); } catch (e) {} |
When recovery is needed |
| catchError | f().catchError((e) => ...) |
Functional style |
| onError | f().then(..., onError: ...) |
Simple callbacks |
5. Future Composition
(1) Future.wait — Concurrent Waiting
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Future.wait Concurrent
import 'dart:async';
Future<List<String>> fetchOrders() async {
await Future.delayed(const Duration(seconds: 3));
return ['ORD-001', 'ORD-002', 'ORD-003'];
}
Future<List<String>> fetchProducts() async {
await Future.delayed(const Duration(seconds: 2));
return ['Laptop', 'Mouse', 'Keyboard'];
}
Future<List<String>> fetchCustomers() async {
await Future.delayed(const Duration(seconds: 2));
return ['Alice', 'Bob', 'Charlie'];
}
void main() async {
// Concurrent: max(3, 2, 2) = ~3 seconds
final stopwatch = Stopwatch()..start();
final results = await Future.wait([
fetchOrders(),
fetchProducts(),
fetchCustomers(),
]);
stopwatch.stop();
print('Orders: ${results[0]}');
print('Products: ${results[1]}');
print('Customers: ${results[2]}');
print('Time: ${stopwatch.elapsedMilliseconds}ms');
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Future.wait Error Handling
import 'dart:async';
Future<String> riskyFetch(String name, bool shouldFail) async {
await Future.delayed(const Duration(seconds: 1));
if (shouldFail) throw Exception('$name failed');
return '$name data';
}
void main() async {
// Future.wait fails fast - if any fails, the whole thing fails
try {
await Future.wait([
riskyFetch('API-1', false),
riskyFetch('API-2', true), // This one fails
riskyFetch('API-3', false),
]);
} catch (e) {
print('Future.wait failed: $e');
}
// Preserve individual results with eagerError: false
final results = await Future.wait(
[
riskyFetch('API-1', false).then((v) => Result.success(v)),
riskyFetch('API-2', true).then((v) => Result.success(v)).catchError((e) => Result.failure(e.toString())),
riskyFetch('API-3', false).then((v) => Result.success(v)),
],
);
for (final r in results) {
print(r.isSuccess ? 'OK: ${r.data}' : 'FAIL: ${r.error}');
}
}
class Result<T> {
final T? data;
final String? error;
final bool isSuccess;
Result.success(this.data) : error = null, isSuccess = true;
Result.failure(this.error) : data = null, isSuccess = false;
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(2) Future.any — Racing
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Future.any Racing
import 'dart:async';
Future<String> fetchFromCache() async {
await Future.delayed(const Duration(milliseconds: 100));
return 'Cache: ORD-001 data';
}
Future<String> fetchFromApi() async {
await Future.delayed(const Duration(seconds: 2));
return 'API: ORD-001 data';
}
Future<String> fetchFromDb() async {
await Future.delayed(const Duration(milliseconds: 500));
return 'DB: ORD-001 data';
}
void main() async {
// Returns the FIRST to complete
final fastest = await Future.any([
fetchFromCache(),
fetchFromApi(),
fetchFromDb(),
]);
print('Fastest: $fastest'); // Cache: ORD-001 data
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(3) Future.forEach — Sequential Iteration
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Future.forEach
import 'dart:async';
Future<void> processOrder(String orderId) async {
await Future.delayed(const Duration(milliseconds: 500));
print('Processed: $orderId');
}
void main() async {
final orders = ['ORD-001', 'ORD-002', 'ORD-003'];
// Process sequentially
await Future.forEach(orders, processOrder);
print('All orders processed');
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
| Composition Method | Behavior | Concurrency | Use Case |
|---|---|---|---|
Future.wait |
Wait for all | Concurrent | Batch requests |
Future.any |
First to complete | Concurrent | Racing/Fallback |
Future.forEach |
Sequential completion | Sequential | Order-dependent operations |
6. Completer Manual Control
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Completer
import 'dart:async';
// Manual Future control with Completer
class ApiService {
final Completer<String> _initCompleter = Completer<String>();
Future<String> get initialized => _initCompleter.future;
void onConnected(String serverInfo) {
if (!_initCompleter.isCompleted) {
_initCompleter.complete(serverInfo);
}
}
void onError(Object error) {
if (!_initCompleter.isCompleted) {
_initCompleter.completeError(error);
}
}
}
void main() async {
final service = ApiService();
// Simulate async initialization
Future.delayed(const Duration(seconds: 1), () {
service.onConnected('Server v3.0.1, 1,000,000 records');
});
print('Waiting for initialization...');
final info = await service.initialized;
print('Connected: $info');
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
7. Bob's Scenario: Concurrent API Requests
▶ Example
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: DataPipeline Concurrent Data Fetching
import 'dart:async';
// Simulated API calls
Future<List<Map<String, dynamic>>> fetchOrders() async {
await Future.delayed(const Duration(seconds: 2));
return [
{'id': 'ORD-001', 'amount': 1500.0, 'category': 'Electronics'},
{'id': 'ORD-002', 'amount': 3200.0, 'category': 'Electronics'},
{'id': 'ORD-003', 'amount': 890.0, 'category': 'Clothing'},
];
}
Future<Map<String, double>> fetchProductPrices() async {
await Future.delayed(const Duration(seconds: 1));
return {'Laptop': 1299.99, 'Mouse': 29.99, 'Keyboard': 79.99};
}
Future<List<String>> fetchCustomerNames() async {
await Future.delayed(const Duration(seconds: 1));
return ['Alice', 'Bob', 'Charlie'];
}
// Concurrent data fetching with error handling
Future<void> generateReport() async {
final stopwatch = Stopwatch()..start();
try {
// Fetch all data concurrently
final results = await Future.wait([
fetchOrders().then((v) => ('orders', v)).catchError((e) => ('orders', null)),
fetchProductPrices().then((v) => ('products', v)).catchError((e) => ('products', null)),
fetchCustomerNames().then((v) => ('customers', v)).catchError((e) => ('customers', null)),
]);
stopwatch.stop();
print('=== DataPipeline Report ===');
print('Fetch time: ${stopwatch.elapsedMilliseconds}ms');
for (final (key, value) in results) {
if (value != null) {
print('$key: OK (${value is List ? value.length : value.length} items)');
} else {
print('$key: FAILED');
}
}
} catch (e) {
print('Report generation failed: $e');
}
}
void main() async {
await generateReport();
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
8. Complete Example: DataPipeline Asynchronous Data Processing
// ============================================
// DataPipeline Async Data Processing
// Future, async/await, and concurrent operations
// ============================================
import 'dart:async';
// Simulated data sources
Future<List<Map<String, dynamic>>> fetchOrders({int delay = 2}) async {
await Future.delayed(Duration(seconds: delay));
return [
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed', 'category': 'Electronics'},
{'id': 'ORD-002', 'amount': 50.0, 'status': 'completed', 'category': 'Books'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending', 'category': 'Electronics'},
{'id': 'ORD-004', 'amount': 890.0, 'status': 'completed', 'category': 'Clothing'},
];
}
Future<List<Map<String, dynamic>>> fetchProducts({int delay = 1}) async {
await Future.delayed(Duration(seconds: delay));
return [
{'name': 'Laptop', 'price': 1299.99, 'category': 'Electronics'},
{'name': 'Mouse', 'price': 29.99, 'category': 'Electronics'},
{'name': 'Novel', 'price': 12.99, 'category': 'Books'},
];
}
Future<double> fetchTaxRate(String region) async {
await Future.delayed(const Duration(milliseconds: 500));
return switch (region) {
'US' => 0.08,
'EU' => 0.20,
'UK' => 0.15,
_ => 0.10,
};
}
// Async pipeline with concurrent operations
class AsyncPipeline {
final String name;
String status = 'idle';
AsyncPipeline({required this.name});
Future<Map<String, dynamic>> run({String region = 'US'}) async {
status = 'running';
final stopwatch = Stopwatch()..start();
try {
// Step 1: Concurrent data fetch
final (orders, products, taxRate) = await (
fetchOrders(),
fetchProducts(),
fetchTaxRate(region),
).wait;
// Step 2: Process orders
final completedOrders = orders
.where((o) => o['status'] == 'completed')
.toList();
// Step 3: Calculate revenue
final revenue = completedOrders.fold<double>(
0, (sum, o) => sum + (o['amount'] as double));
final taxAmount = revenue * taxRate;
final totalWithTax = revenue + taxAmount;
// Step 4: Group by category
final byCategory = <String, double>{};
for (final order in completedOrders) {
final cat = order['category'] as String;
final amt = order['amount'] as double;
byCategory.update(cat, (v) => v + amt, ifAbsent: () => amt);
}
stopwatch.stop();
status = 'completed';
return {
'pipeline': name,
'region': region,
'taxRate': taxRate,
'totalOrders': orders.length,
'completedOrders': completedOrders.length,
'revenue': revenue,
'tax': taxAmount,
'totalWithTax': totalWithTax,
'byCategory': byCategory,
'processingTime': stopwatch.elapsedMilliseconds,
'status': status,
};
} catch (e) {
status = 'failed';
rethrow;
}
}
}
void main() async {
final pipeline = AsyncPipeline(name: 'E-Commerce Analytics');
print('=== Starting DataPipeline ===');
final report = await pipeline.run(region: 'US');
print('\n=== Report ===');
print('Pipeline: ${report['pipeline']}');
print('Region: ${report['region']}');
print('Tax Rate: ${(report['taxRate'] as double * 100).toStringAsFixed(1)}%');
print('Total Orders: ${report['totalOrders']}');
print('Completed: ${report['completedOrders']}');
print('Revenue: \$${(report['revenue'] as double).toStringAsFixed(2)} USD');
print('Tax: \$${(report['tax'] as double).toStringAsFixed(2)} USD');
print('Total: \$${(report['totalWithTax'] as double).toStringAsFixed(2)} USD');
print('Time: ${report['processingTime']}ms');
print('\nBy Category:');
for (final entry in (report['byCategory'] as Map<String, double>).entries) {
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
}
}
> **Output:** Execute in DartPad or using `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
Output:
=== Starting DataPipeline ===
=== Report ===
Pipeline: E-Commerce Analytics
Region: US
Tax Rate: 8.0%
Total Orders: 4
Completed: 3
Revenue: $2440.00 USD
Tax: $195.20 USD
Total: $2635.20 USD
Time: 2000ms
By Category:
Electronics: $1500.00 USD
Books: $50.00 USD
Clothing: $890.00 USD
❓ FAQ
Q: What is the return type of an async function? A: An async function automatically wraps the return value into a Future. Declare a
Future<T>return type, and simply return T inside the function.
Q: Can await only be used in async functions? A: Yes. await can only be used in async functions or async* generators. Dart 3.5+ supports top-level await (only in Dart scripts).
Q: What happens if a Future fails in Future.wait? A: By default, if any Future fails, Future.wait immediately throws that exception (other Futures continue running but their results are ignored). You can use
eagerError: falseto wait for all to complete.
Q: What is the difference between async/await and .then()? A: They are functionally equivalent, but async/await is more readable (linear flow), while .then() is more functional (chaining). For daily use, async/await is recommended; .then() is suitable for simple transformations.
Q: When should I use Completer? A: Use Completer when you need to manually control the completion timing of a Future. Common scenarios: converting event callbacks to Futures, WebSocket messages, third-party library callbacks. In most cases, you can just use async/await.
Q: Is Future multicast or unicast? A: A Future is unicast — it can only be consumed by one await or .then(). For multicast, you need a Stream or asStream().
Q: How to implement timeout control? A: Use the Future.timeout() method. For example:
await fetchApi().timeout(Duration(seconds: 5), onTimeout: () => defaultData).
📖 Summary
- A Future represents the eventual result of an asynchronous computation, with three states: pending/completed/failed.
- async/await is syntactic sugar for Future, making asynchronous code read like synchronous code.
- Future.wait executes multiple Futures concurrently, with total time determined by the slowest one.
- Future.any races to get the fastest one; Future.forEach executes sequentially.
- Completer manually controls Future completion, suitable for converting callbacks to Futures.
📝 Exercises
- Basic (⭐ Difficulty): Write 3 functions that return Futures (each with a delay of 1/2/3 seconds). Call them sequentially using async/await and measure the total time. Then call them concurrently using Future.wait and compare the times.
- Intermediate (⭐⭐ Difficulty): Implement an asynchronous request function with retry:
fetchWithRetry(url, retries: 3, backoff: Duration). It should retry on failure with exponential backoff and throw an exception after exceeding the retry limit. - Challenge (⭐⭐⭐ Difficulty): Implement a simple "request-response" protocol using Completer: send a request and wait for the corresponding response, with timeout support. Simulate a message bus where multiple requests can be sent concurrently.