Dart Futures
Handle asynchronous operations that complete in the future
š® What are Dart Futures?
Futures represent values or errors that will be available at some point in the future. They're perfect for handling asynchronous operations like network requests, file operations, or any task that takes time to complete.
Future<String> fetchUserName() {
return Future.delayed(
Duration(seconds: 2),
() => 'John Doe'
);
}
void main() async {
print('Fetching user...');
String name = await fetchUserName();
print('User: $name');
}
Output:
Fetching user...
(2 seconds later)
User: John Doe
Future Concepts
Pending State
Future is waiting to complete
Future<String> pending =
Future.delayed(Duration(seconds: 1));
Completed State
Future finished with a value
Future<int> completed =
Future.value(42);
Error State
Future completed with an error
Future<String> error =
Future.error('Something went wrong');
Chaining
Chain multiple async operations
future.then((value) => process(value))
.then((result) => save(result));
š¹ Creating Futures
Different ways to create Future objects:
void main() async {
print('=== Creating Futures ===');
// 1. Future.value - immediately completed future
Future<String> immediateFuture = Future.value('Hello World');
String result1 = await immediateFuture;
print('Immediate: $result1');
// 2. Future.delayed - future that completes after a delay
Future<int> delayedFuture = Future.delayed(
Duration(seconds: 1),
() => 42
);
int result2 = await delayedFuture;
print('Delayed: $result2');
// 3. Future.error - future that completes with an error
Future<String> errorFuture = Future.error('Oops!');
try {
await errorFuture;
} catch (e) {
print('Error caught: $e');
}
// 4. Custom future with computation
Future<double> calculatePi() {
return Future(() {
// Simulate complex calculation
double pi = 0;
for (int i = 0; i < 1000000; i++) {
pi += (i % 2 == 0 ? 1 : -1) / (2 * i + 1);
}
return pi * 4;
});
}
print('Calculating Ļ...');
double pi = await calculatePi();
print('Ļ ā ${pi.toStringAsFixed(6)}');
// 5. Future from async function
Future<List<String>> getShoppingList() async {
await Future.delayed(Duration(milliseconds: 500));
return ['Apples', 'Bananas', 'Oranges'];
}
List<String> shopping = await getShoppingList();
print('Shopping list: $shopping');
}
Output:
=== Creating Futures ===
Immediate: Hello World
Delayed: 42
Error caught: Oops!
Calculating Ļ...
Ļ ā 3.141591
Shopping list: [Apples, Bananas, Oranges]
š¹ Future Methods: then, catchError, whenComplete
Handle futures without async/await:
Future<int> riskyCalculation(int input) {
return Future.delayed(Duration(seconds: 1), () {
if (input < 0) {
throw Exception('Negative numbers not allowed!');
}
return input * input;
});
}
Future<String> formatResult(int number) {
return Future.delayed(Duration(milliseconds: 500), () {
return 'The result is: $number';
});
}
void main() {
print('=== Future Methods Demo ===');
// Example 1: Successful chain
print('\n--- Success Case ---');
riskyCalculation(5)
.then((result) {
print('Calculation result: $result');
return formatResult(result);
})
.then((formatted) {
print('Formatted: $formatted');
})
.catchError((error) {
print('Error in chain: $error');
})
.whenComplete(() {
print('Success chain completed');
});
// Example 2: Error case
print('\n--- Error Case ---');
riskyCalculation(-3)
.then((result) {
print('This won\'t be printed');
return formatResult(result);
})
.then((formatted) {
print('This won\'t be printed either');
})
.catchError((error) {
print('Caught error: $error');
return 'Error handled gracefully';
})
.then((recovery) {
print('Recovery: $recovery');
})
.whenComplete(() {
print('Error chain completed');
});
// Example 3: Multiple operations
print('\n--- Multiple Operations ---');
Future.wait([
riskyCalculation(3),
riskyCalculation(4),
riskyCalculation(5),
]).then((results) {
print('All calculations: $results');
int sum = results.reduce((a, b) => a + b);
return sum;
}).then((sum) {
print('Sum of squares: $sum');
}).catchError((error) {
print('Error in batch: $error');
});
// Keep main alive to see results
Future.delayed(Duration(seconds: 3), () {
print('\n=== Demo completed ===');
});
}
Output:
=== Future Methods Demo ===
--- Success Case ---
Calculation result: 25
Formatted: The result is: 25
Success chain completed
--- Error Case ---
Caught error: Exception: Negative numbers not allowed!
Recovery: Error handled gracefully
Error chain completed
--- Multiple Operations ---
All calculations: [9, 16, 25]
Sum of squares: 50
=== Demo completed ===
š¹ Future.wait and Parallel Execution
Run multiple futures concurrently:
Future<String> fetchUserData(String userId) async {
await Future.delayed(Duration(seconds: 1));
return 'User data for $userId';
}
Future<List<String>> fetchUserPosts(String userId) async {
await Future.delayed(Duration(seconds: 2));
return ['Post 1 by $userId', 'Post 2 by $userId'];
}
Future<int> fetchUserFollowers(String userId) async {
await Future.delayed(Duration(milliseconds: 800));
return userId.hashCode % 1000; // Fake follower count
}
void main() async {
String userId = 'user123';
print('=== Sequential vs Parallel Execution ===');
// Sequential execution
print('\n--- Sequential (slow) ---');
var start = DateTime.now();
String userData = await fetchUserData(userId);
List<String> userPosts = await fetchUserPosts(userId);
int followers = await fetchUserFollowers(userId);
var sequentialTime = DateTime.now().difference(start);
print('User: $userData');
print('Posts: $userPosts');
print('Followers: $followers');
print('Sequential time: ${sequentialTime.inMilliseconds}ms');
// Parallel execution with Future.wait
print('\n--- Parallel with Future.wait (fast) ---');
start = DateTime.now();
List<dynamic> results = await Future.wait([
fetchUserData(userId),
fetchUserPosts(userId),
fetchUserFollowers(userId),
]);
var parallelTime = DateTime.now().difference(start);
print('User: ${results[0]}');
print('Posts: ${results[1]}');
print('Followers: ${results[2]}');
print('Parallel time: ${parallelTime.inMilliseconds}ms');
// Parallel with error handling
print('\n--- Parallel with Error Handling ---');
Future<String> faultyOperation() async {
await Future.delayed(Duration(milliseconds: 500));
throw Exception('Something went wrong!');
}
try {
var mixedResults = await Future.wait([
fetchUserData(userId),
faultyOperation(),
fetchUserFollowers(userId),
], eagerError: true); // Stop on first error
print('This won\'t be printed');
} catch (e) {
print('Caught error in parallel execution: $e');
}
// Using Future.wait with error recovery
print('\n--- Parallel with Individual Error Handling ---');
var safeResults = await Future.wait([
fetchUserData(userId).catchError((e) => 'Error: $e'),
faultyOperation().catchError((e) => 'Error: $e'),
fetchUserFollowers(userId).catchError((e) => -1),
]);
print('Safe results: $safeResults');
double speedup = sequentialTime.inMilliseconds / parallelTime.inMilliseconds;
print('\nSpeedup: ${speedup.toStringAsFixed(1)}x faster!');
}
Output:
=== Sequential vs Parallel Execution ===
--- Sequential (slow) ---
User: User data for user123
Posts: [Post 1 by user123, Post 2 by user123]
Followers: 456
Sequential time: 3800ms
--- Parallel with Future.wait (fast) ---
User: User data for user123
Posts: [Post 1 by user123, Post 2 by user123]
Followers: 456
Parallel time: 2000ms
--- Parallel with Error Handling ---
Caught error in parallel execution: Exception: Something went wrong!
--- Parallel with Individual Error Handling ---
Safe results: [User data for user123, Error: Exception: Something went wrong!, 456]
Speedup: 1.9x faster!
š¹ Real-World Example: API Client
A practical example of using Futures for API operations:
import 'dart:math';
// Simulate API responses
class ApiResponse<T> {
final bool success;
final T? data;
final String? error;
ApiResponse.success(this.data) : success = true, error = null;
ApiResponse.error(this.error) : success = false, data = null;
}
class User {
final String id;
final String name;
final String email;
User(this.id, this.name, this.email);
@override
String toString() => 'User($id, $name, $email)';
}
class ApiClient {
final Random _random = Random();
// Simulate network delay and occasional failures
Future<T> _simulateNetworkCall<T>(T data, {int delayMs = 1000, double failureRate = 0.1}) {
return Future.delayed(Duration(milliseconds: delayMs), () {
if (_random.nextDouble() < failureRate) {
throw Exception('Network error occurred');
}
return data;
});
}
Future<ApiResponse<User>> getUser(String userId) async {
try {
print('API: Fetching user $userId...');
var userData = await _simulateNetworkCall(
User(userId, 'User $userId', '[email protected]'),
delayMs: 800,
);
print('API: User $userId fetched successfully');
return ApiResponse.success(userData);
} catch (e) {
print('API: Failed to fetch user $userId: $e');
return ApiResponse.error('Failed to fetch user: $e');
}
}
Future<ApiResponse<List<String>>> getUserPosts(String userId) async {
try {
print('API: Fetching posts for $userId...');
var posts = await _simulateNetworkCall(
['Post 1 by $userId', 'Post 2 by $userId', 'Post 3 by $userId'],
delayMs: 1200,
);
print('API: Posts for $userId fetched successfully');
return ApiResponse.success(posts);
} catch (e) {
print('API: Failed to fetch posts for $userId: $e');
return ApiResponse.error('Failed to fetch posts: $e');
}
}
Future<ApiResponse<Map<String, int>>> getUserStats(String userId) async {
try {
print('API: Fetching stats for $userId...');
var stats = await _simulateNetworkCall({
'followers': _random.nextInt(1000),
'following': _random.nextInt(500),
'posts': _random.nextInt(100),
}, delayMs: 600);
print('API: Stats for $userId fetched successfully');
return ApiResponse.success(stats);
} catch (e) {
print('API: Failed to fetch stats for $userId: $e');
return ApiResponse.error('Failed to fetch stats: $e');
}
}
}
class UserService {
final ApiClient _apiClient = ApiClient();
Future<Map<String, dynamic>> getUserProfile(String userId) async {
print('Service: Loading complete profile for $userId...');
try {
// Fetch all user data in parallel
var results = await Future.wait([
_apiClient.getUser(userId),
_apiClient.getUserPosts(userId),
_apiClient.getUserStats(userId),
]);
var userResponse = results[0] as ApiResponse<User>;
var postsResponse = results[1] as ApiResponse<List<String>>;
var statsResponse = results[2] as ApiResponse<Map<String, int>>;
// Check if all requests succeeded
if (!userResponse.success) {
throw Exception(userResponse.error);
}
if (!postsResponse.success) {
throw Exception(postsResponse.error);
}
if (!statsResponse.success) {
throw Exception(statsResponse.error);
}
return {
'user': userResponse.data,
'posts': postsResponse.data,
'stats': statsResponse.data,
'loadedAt': DateTime.now().toIso8601String(),
};
} catch (e) {
print('Service: Failed to load profile for $userId: $e');
rethrow;
}
}
}
void main() async {
var userService = UserService();
print('=== User Profile Loading Demo ===');
try {
var profile = await userService.getUserProfile('john123');
print('\nā
Profile loaded successfully!');
print('User: ${profile['user']}');
print('Posts: ${profile['posts']}');
print('Stats: ${profile['stats']}');
print('Loaded at: ${profile['loadedAt']}');
} catch (e) {
print('\nā Failed to load profile: $e');
}
// Try loading multiple users
print('\n=== Loading Multiple Users ===');
var userIds = ['alice', 'bob', 'charlie'];
var futures = userIds.map((id) => userService.getUserProfile(id)).toList();
var results = await Future.wait(
futures.map((future) => future.catchError((e) => {'error': e.toString()})),
);
for (int i = 0; i < userIds.length; i++) {
var userId = userIds[i];
var result = results[i];
if (result.containsKey('error')) {
print('ā $userId: ${result['error']}');
} else {
print('ā
$userId: Profile loaded with ${result['posts'].length} posts');
}
}
}
Output:
=== User Profile Loading Demo ===
Service: Loading complete profile for john123...
API: Fetching user john123...
API: Fetching posts for john123...
API: Fetching stats for john123...
API: Stats for john123 fetched successfully
API: User john123 fetched successfully
API: Posts for john123 fetched successfully
ā Profile loaded successfully!
User: User(john123, User john123, [email protected])
Posts: [Post 1 by john123, Post 2 by john123, Post 3 by john123]
Stats: {followers: 234, following: 156, posts: 42}
Loaded at: 2024-01-15T14:30:45.123456
=== Loading Multiple Users ===
Service: Loading complete profile for alice...
Service: Loading complete profile for bob...
Service: Loading complete profile for charlie...
ā alice: Profile loaded with 3 posts
ā bob: Profile loaded with 3 posts
ā charlie: Exception: Network error occurred