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

🧠 Test Your Knowledge

What method would you use to run multiple futures concurrently?