Skip to content

Asynchronous Programming

Asynchronous programming allows your program to perform tasks without blocking execution. Dart provides excellent support for async operations through Future, async, and await.

Future

A Future represents a value that will be available at some point in the future.

dart
Future<String> fetchUserData() {
  return Future.delayed(
    Duration(seconds: 2),
    () => 'User data loaded',
  );
}

void main() {
  print('Fetching data...');
  fetchUserData().then((data) {
    print(data);
  });
  print('Request sent');
}
// Output:
// Fetching data...
// Request sent
// User data loaded (after 2 seconds)

async and await

The async and await keywords make asynchronous code look synchronous:

dart
Future<String> fetchUserData() async {
  await Future.delayed(Duration(seconds: 2));
  return 'User data loaded';
}

void main() async {
  print('Fetching data...');
  String data = await fetchUserData();
  print(data);
  print('Done');
}

Error Handling

dart
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  throw Exception('Failed to load data');
}

void main() async {
  try {
    String data = await fetchData();
    print(data);
  } catch (e) {
    print('Error: $e');
  } finally {
    print('Cleanup');
  }
}

Multiple Futures

Future.wait

Wait for multiple futures to complete:

dart
Future<void> loadData() async {
  var results = await Future.wait([
    fetchUser(),
    fetchPosts(),
    fetchComments(),
  ]);
  
  print('User: ${results[0]}');
  print('Posts: ${results[1]}');
  print('Comments: ${results[2]}');
}

Streams

Streams provide a sequence of asynchronous events:

dart
Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

void main() async {
  await for (var number in countStream(5)) {
    print(number);
  }
}

Stream Methods

dart
void main() async {
  Stream<int> stream = Stream.fromIterable([1, 2, 3, 4, 5]);
  
  // Listen
  stream.listen((value) {
    print(value);
  });
  
  // Transform
  var doubled = stream.map((n) => n * 2);
  
  // Filter
  var evens = stream.where((n) => n % 2 == 0);
}

Complete Example

dart
class ApiService {
  Future<Map<String, dynamic>> fetchUser(int id) async {
    await Future.delayed(Duration(seconds: 1));
    return {'id': id, 'name': 'User $id', 'email': 'user$id@example.com'};
  }
  
  Future<List<String>> fetchPosts(int userId) async {
    await Future.delayed(Duration(seconds: 1));
    return ['Post 1', 'Post 2', 'Post 3'];
  }
  
  Stream<String> fetchComments(int postId) async* {
    for (int i = 1; i <= 3; i++) {
      await Future.delayed(Duration(milliseconds: 500));
      yield 'Comment $i on post $postId';
    }
  }
}

void main() async {
  var api = ApiService();
  
  try {
    // Fetch user
    print('Fetching user...');
    var user = await api.fetchUser(1);
    print('User: ${user['name']}');
    
    // Fetch posts
    print('\nFetching posts...');
    var posts = await api.fetchPosts(user['id']);
    print('Posts: ${posts.length}');
    
    // Stream comments
    print('\nFetching comments...');
    await for (var comment in api.fetchComments(1)) {
      print(comment);
    }
    
    print('\nAll data loaded!');
  } catch (e) {
    print('Error: $e');
  }
}

Best Practices

  1. Always use async/await for cleaner code
  2. Handle errors with try-catch
  3. Use Future.wait for parallel operations
  4. Prefer streams for continuous data
  5. Don't forget await - common mistake!

Next Steps

Content is for learning and research only.