Lesson 25 min
Futures & Async/Await
00:00 / 00:00
What is a Future?
A Future<T> represents a value that will be available at some point in the future — the result of an asynchronous operation. It is Dart's equivalent of JavaScript's Promise.
A Future is always in one of three states:
- Uncompleted — waiting for the operation to finish
- Completed with a value — success
- Completed with an error — failure
dart// A Future that completes with a String after 2 seconds Future<String> fetchGreeting() async { await Future.delayed(const Duration(seconds: 2)); return 'Hello from the future!'; }
async / await
async and await are syntactic sugar that make asynchronous code look and read like synchronous code:
dart// Without async/await (callback hell) fetchUser(id) .then((user) => fetchPosts(user.id)) .then((posts) => print(posts)) .catchError((e) => print('Error: $e')); // With async/await (clean and readable) Future<void> loadData(String id) async { try { final user = await fetchUser(id); // suspends here final posts = await fetchPosts(user.id); // suspends here print(posts); } catch (e) { print('Error: $e'); } }
Future Combinators
dart// Run multiple Futures in PARALLEL — wait for all to complete Future<void> loadDashboard() async { final results = await Future.wait([ fetchUser(), fetchNotifications(), fetchStats(), ]); // results[0] = user, results[1] = notifications, results[2] = stats } // First to complete wins final fastest = await Future.any([ fetchFromServer1(), fetchFromServer2(), fetchFromCache(), ]); // Complete after a delay await Future.delayed(const Duration(seconds: 1)); // Already-completed Future (useful for testing) final immediate = Future.value(42); final failed = Future.error(Exception('immediate failure'));
Error Handling Patterns
dart// Pattern 1: try/catch Future<User> getUser(String id) async { try { final response = await apiClient.get('/users/$id'); return User.fromJson(response.data); } on NetworkException { throw UserFetchException('No network'); } on ApiException catch (e) { throw UserFetchException('API error: ${e.message}'); } } // Pattern 2: Result type (functional approach) Future<Result<User>> getUserSafe(String id) async { try { final user = await getUser(id); return Success(user); } catch (e) { return Failure(e.toString()); } } // Usage: final result = await getUserSafe('123'); switch (result) { case Success(:final value): showUser(value); case Failure(:final error): showError(error); }
FutureBuilder in Flutter
dartFutureBuilder<User>( future: fetchUser(userId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } if (snapshot.hasData) { return UserCard(user: snapshot.data!); } return const SizedBox.shrink(); }, )
Summary
async/await is the single most important async pattern in Dart. It makes asynchronous code readable, maintainable, and easy to reason about. Combined with Future.wait for parallel operations and proper error handling, it covers 90% of real-world asynchronous use cases.