© Khmer Angkor Academy - sophearithput168

Async Programming

Asynchronous Programming ក្នុង Dart

Async programming អនុញ្ញាតឱ្យកូដរត់ដោយមិនបង្អាក់ UI thread។ សំខាន់ណាស់សម្រាប់ operations ដែលចំណាយពេល (API calls, file I/O, database queries)។

📚 Async Theory (Event Loop Model)

Dart Concurrency Model:

Dart Event Loop (Single-Threaded)

┌─────────────────────────────────┐
│     Main Thread (UI Thread)     │
├─────────────────────────────────┤
│                                 │
│  1️⃣ Synchronous Code            │
│     (Runs immediately)          │
│                                 │
│  2️⃣ Microtask Queue             │
│     (High priority async tasks) │
│                                 │
│  3️⃣ Event Queue                 │
│     (Futures, I/O, Timers)      │
│                                 │
└─────────────────────────────────┘

Process Order:
1. Execute sync code
2. Process all microtasks
3. Process one event from event queue
4. Repeat from step 2

Synchronous vs Asynchronous:

Synchronous Asynchronous
Blocks execution (waiting) Non-blocking (continues)
Runs in order (sequential) Runs when ready (concurrent)
UI freezes during operation UI stays responsive
Example: calculations, loops Example: HTTP, file I/O, timers

⏳ ហេតុអ្វីត្រូវការ Async?

  • 📡 Network Requests: API calls, downloading data
  • 📁 File Operations: Reading/writing files
  • 💾 Database: Querying local/remote databases
  • Timers: Delayed operations, animations
  • 🎯 Heavy Computations: Image processing, parsing

⚠️ What happens without async?

UI freezes, app becomes unresponsive, bad user experience. Users might think app crashed!

🔮 Future (Promise of a Value)

Future តំណាងឱ្យតម្លៃដែលនឹងមានក្នុងអនាគត។ វាអាចមាន 3 states:

State Description
Uncompleted ⏳ Operation still running (pending)
Completed with value ✅ Operation succeeded
Completed with error ❌ Operation failed

Basic Future (Using .then())

Future<String> fetchUserName() {
  // Simulate network delay
  return Future.delayed(
    Duration(seconds: 2),
    () => 'សុខា'
  );
}

void main() {
  print('ចាប់ផ្តើម...');
  
  fetchUserName().then((name) {
    print('ឈ្មោះ៖ $name');
  });
  
  print('កំពុងរង់ចាំ...');
}

// Output:
// ចាប់ផ្តើម...
// កំពុងរង់ចាំ...
// ឈ្មោះ៖ សុខា (បន្ទាប់ពី 2 វិនាទី)

async/await

Future<String> fetchUserName() async {
  await Future.delayed(Duration(seconds: 2));
  return 'សុខា';
}

Future<int> fetchAge() async {
  await Future.delayed(Duration(seconds: 1));
  return 20;
}

Future<void> getUserInfo() async {
  print('ចាប់ផ្តើម...');
  
  String name = await fetchUserName();
  print('ឈ្មោះ៖ $name');
  
  int age = await fetchAge();
  print('អាយុ៖ $age');
  
  print('បញ្ចប់!');
}

void main() async {
  await getUserInfo();
}

async/await vs .then() Comparison:

async/await .then()
✅ Easier to read (looks synchronous) ⚠️ Callback hell with nested then()
✅ Better error handling (try-catch) ⚠️ .catchError() can be messy
✅ Recommended for modern Dart/Flutter ⚠️ Older style, but still valid

⚠️ Error Handling (Critical!)

Always handle errors in async operations to prevent app crashes!

try-catch with async/await (Recommended)

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  throw Exception('មានបញ្ហា!');
}

Future<void> getData() async {
  try {
    String data = await fetchData();
    print('Data: $data');
  } catch (e) {
    print('Error: $e');
  } finally {
    print('បញ្ចប់ការដំណើរការ');
  }
}

void main() async {
  await getData();
}

// Output:
// Error: Exception: មានបញ្ហា!
// បញ្ចប់ការដំណើរការ

catchError with then()

Future<String> fetchData() {
  return Future.delayed(
    Duration(seconds: 1),
    () => throw Exception('Connection failed')
  );
}

void main() {
  fetchData()
    .then((data) {
      print('Success: $data');
    })
    .catchError((error) {
      print('Error: $error');
    })
    .whenComplete(() {
      print('Done');
    });
}

🔄 Multiple Futures

Future.wait() - រង់ចាំទាំងអស់

Future<String> fetchName() async {
  await Future.delayed(Duration(seconds: 2));
  return 'សុខា';
}

Future<int> fetchAge() async {
  await Future.delayed(Duration(seconds: 1));
  return 20;
}

Future<String> fetchCity() async {
  await Future.delayed(Duration(seconds: 3));
  return 'ភ្នំពេញ';
}

Future<void> getAllData() async {
  print('កំពុងទាញយក...');
  
  // រត់ parallel និងរង់ចាំទាំងអស់
  List<dynamic> results = await Future.wait([
    fetchName(),
    fetchAge(),
    fetchCity()
  ]);
  
  print('ឈ្មោះ៖ ' + results[0].toString());
  print('អាយុ៖ ' + results[1].toString());
  print('ទីក្រុង៖ ' + results[2].toString());
}

void main() async {
  await getAllData();
  // ចំណាយ 3 វិនាទី (មិនមែន 2+1+3=6 វិនាទីទេ)
}

📡 Practical Example - API Call

import 'dart:convert';
import 'package:http/http.dart' as http;

class User {
  final int id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email']
    );
  }
}

Future<User> fetchUser(int id) async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/users/$id')
  );
  
  if (response.statusCode == 200) {
    Map<String, dynamic> data = jsonDecode(response.body);
    return User.fromJson(data);
  } else {
    throw Exception('Failed to load user');
  }
}

void main() async {
  try {
    User user = await fetchUser(1);
    print('ID: ' + user.id.toString());
    print('Name: ' + user.name);
    print('Email: ' + user.email);
  } catch (e) {
    print('Error: $e');
  }
}

⏱️ Stream - Async Data Flow

Stream គឺជា sequence of asynchronous events។ ប្រើសម្រាប់ data ដែលមកជាបន្តបន្ទាប់ (multiple values over time)។

Future vs Stream:

Future Stream
Single value Multiple values (sequence)
Completes once Can emit many times
Example: HTTP request Example: WebSocket, real-time updates
await keyword await for or listen()

When to Use Streams?

  • 🔴 Real-time Data: Chat messages, live scores
  • 📡 WebSocket: Continuous server connection
  • 📹 Video/Audio: Media streaming
  • 📍 Location Updates: GPS tracking
  • 🎮 User Events: Button clicks, gestures
  • 📊 Firestore: Real-time database listeners

Basic Stream (async* and yield)

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

void main() async {
  print('ចាប់ផ្តើមរាប់...');
  
  await for (int count in countStream(5)) {
    print('Count: $count');
  }
  
  print('បញ្ចប់!');
}

// Output (រៀងរាល់ 1 វិនាទី):
// ចាប់ផ្តើមរាប់...
// Count: 1
// Count: 2
// Count: 3
// Count: 4
// Count: 5
// បញ្ចប់!

Stream Methods

Stream<int> numberStream() async* {
  for (int i = 1; i <= 10; i++) {
    yield i;
  }
}

void main() async {
  // listen()
  numberStream().listen(
    (number) {
      print('Number: $number');
    },
    onError: (error) {
      print('Error: $error');
    },
    onDone: () {
      print('Stream closed');
    }
  );
  
  // where() - filter
  await for (int even in numberStream().where((n) => n % 2 == 0)) {
    print('Even: $even');  // 2, 4, 6, 8, 10
  }
  
  // map() - transform
  await for (String text in numberStream().map((n) => 'Number $n')) {
    print(text);  // Number 1, Number 2, ...
  }
  
  // take() - limit
  await for (int num in numberStream().take(3)) {
    print(num);  // 1, 2, 3
  }
}

StreamController

import 'dart:async';

void main() async {
  // បង្កើត StreamController
  StreamController<String> controller = StreamController<String>();
  
  // Listen to stream
  controller.stream.listen((data) {
    print('Received: $data');
  });
  
  // បញ្ចូល data ទៅ stream
  controller.sink.add('សួស្តី');
  controller.sink.add('Flutter');
  controller.sink.add('Cambodia');
  
  // បិទ stream
  await controller.close();
}

StreamController Types:

Type Description Use Case
Single-subscription One listener only Reading file, HTTP response
Broadcast Multiple listeners Events, notifications, UI updates
// Broadcast Stream
StreamController<int> controller = StreamController<int>.broadcast();

controller.stream.listen((data) => print('Listener 1: ' + data.toString()));
controller.stream.listen((data) => print('Listener 2: ' + data.toString()));

controller.add(5);
// Output:
// Listener 1: 5
// Listener 2: 5

🎯 Real-world Example - Search with Debouncing

import 'dart:async';

class SearchService {
  final StreamController<String> _searchController = StreamController<String>();
  
  Stream<List<String>> get searchResults {
    return _searchController.stream
      .debounceTime(Duration(milliseconds: 500))  // រង់ចាំ user ចប់វាយ
      .distinct()                                  // លុប duplicates
      .asyncMap((query) => _performSearch(query));
  }
  
  void search(String query) {
    _searchController.sink.add(query);
  }
  
  Future<List<String>> _performSearch(String query) async {
    // Simulate API call
    await Future.delayed(Duration(seconds: 1));
    
    List<String> allItems = [
      'ផ្លែប៉ោម', 'ផ្លែចេក', 'ស្វាយ', 'ទ្រាប់', 'ម្នាស់'
    ];
    
    return allItems.where((item) => item.contains(query)).toList();
  }
  
  void dispose() {
    _searchController.close();
  }
}

void main() async {
  SearchService service = SearchService();
  
  service.searchResults.listen((results) {
    print('Results: $results');
  });
  
  service.search('ផ្លែ');
  
  await Future.delayed(Duration(seconds: 2));
  service.dispose();
}

⚡ Advanced Async Patterns

1. Retry Pattern (Auto-Retry Failed Operations)

Future<T> retry<T>(
  Future<T> Function() operation, {
  int maxAttempts = 3,
  Duration delay = const Duration(seconds: 1),
}) async {
  int attempts = 0;
  
  while (attempts < maxAttempts) {
    try {
      return await operation();
    } catch (e) {
      attempts++;
      if (attempts >= maxAttempts) {
        throw Exception('Failed after ' + maxAttempts.toString() + ' attempts');
      }
      print('Attempt ' + attempts.toString() + ' failed. Retrying...');
      await Future.delayed(delay);
    }
  }
  throw Exception('Should not reach here');
}

// Usage
void main() async {
  try {
    var result = await retry(() => fetchDataFromAPI());
    print('Success: ' + result.toString());
  } catch (e) {
    print('All retries failed: ' + e.toString());
  }
}

2. Timeout Pattern

Future<String> fetchWithTimeout() async {
  return await Future.delayed(Duration(seconds: 5), () => 'Data')
    .timeout(
      Duration(seconds: 3),
      onTimeout: () {
        throw TimeoutException('Request took too long!');
      },
    );
}

void main() async {
  try {
    String data = await fetchWithTimeout();
    print(data);
  } on TimeoutException {
    print('Request timed out!');
  }
}

3. Race Pattern (First to Complete)

Future<String> fetchFromPrimaryServer() async {
  await Future.delayed(Duration(seconds: 3));
  return 'Primary data';
}

Future<String> fetchFromBackupServer() async {
  await Future.delayed(Duration(seconds: 1));
  return 'Backup data';
}

void main() async {
  // Get result from whichever completes first
  String result = await Future.any([
    fetchFromPrimaryServer(),
    fetchFromBackupServer(),
  ]);
  
  print(result);  // 'Backup data' (faster)
}

💡 Best Practices:

  • ✅ Always use async/await for better readability
  • ✅ Always handle errors with try-catch
  • ✅ Use Future.wait() for parallel operations
  • ✅ Close StreamControllers in dispose()
  • ✅ Use Streams for continuous data, Futures for one-time operations
  • ✅ Add timeout to prevent hanging operations
  • ✅ Show loading indicators during async operations

⚠️ Common Async Mistakes:

  • ❌ Forgetting await keyword (code runs synchronously)
  • ❌ Not handling errors (app crashes)
  • ❌ Sequential instead of parallel execution (slow)
  • ❌ Not closing StreamControllers (memory leak)
  • ❌ Blocking UI thread with heavy sync operations
  • ❌ Using .then() when async/await is clearer

💡 ជំនួយ: ក្នុង Flutter អ្នកនឹងប្រើ async/await ជាច្រើនសម្រាប់ API calls, database queries និង file operations។ Stream ប្រើសម្រាប់ real-time data ដូចជា user input, WebSockets, Firestore listeners។ Master async programming for responsive apps!