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!