Day 12: Asynchronous Dart - Futures
Learn asynchronous Dart and Futures - THE foundational skill you need before building any real Flutter app. From basics to advanced patterns in pure Dart.
မင်္ဂလာပါ.. 👋
100 days of Flutter ရဲ့ Day 12 က ကြိုဆိုပါတယ်။ ဒီနေ့မှာတော့ dart langauge နဲ့ ပတ်သက်တာတွေကို ဆက်လက်ပြီး လေ့လာသွားပါမယ်။ ဒီနေ့ဆွေးနွေးသွားမယ့် ခေါင်းစဥ်ကတော့ Asynchronous Dart & Futures အကြောင်းပဲ ဖြစ်ပါတယ်။
App တွေ တည်ဆောက်တဲ့အချိန် API ခေါ်တဲ့အခါ၊ database က data တွေ ဖတ်တဲ့အခါ စောင့်ရတာမျိုးတွေရှိပါတယ်။ ဒီလိုမျိုး စောင့်ရတဲ့အခါ app ကြီး တခုလုံး အလုပ်မလုပ်တော့တာမျိုး မဖြစ်အောင် ရေးမယ်ဆိုရင်တော့ asynchronous ဆိုတာ နားလည်ဖို့ လိုပါတယ်။ API ခေါ်နေတဲ့အချိန် app ကြီးက ရပ်နေပြီး ဘာမှ လုပ်မရတော့တာမျိုးဆို အသုံးပြုသူတွေအတွက် အများကြီး အနှောင့်အယှက် ဖြစ်စေနိုင်ပါတယ်။
ဒီတော့ အောက်မှာ ပေးထားတဲ့ code ကို တချက်ကြည့်လိုက်ကြရအောင်ပါ။ ဒီမှာ အလုပ်လုပ်တာက burger မှာပြီးတော့ ထိုင်စောင့်တာမျိုးကို ဥပမာ ပေးထားတာပါ။
import 'dart:io';
Future<void> orderFood() async {
print('Ordering burger...');
sleep(Duration(seconds: 5));
print('Got burger!');
}
void main() {
print('1. Go to restaurant');
orderFood();
print('2. Sit down, check phone');
print('3. Reply to messages');
print('4. Browse Instagram');
}
// Output:
// 1. Go to restaurant
// Ordering burger...
// Got burger!
// 2. Sit down, check phone
// 3. Reply to messages
// 4. Browse Instagramsleep ဆိုတဲ့ function ကတော့ dart မှာ built-in ပါပြီးသားပါ။ ဒါကို ခေါ်လိုက်ခြင်းအားဖြင့် program ကို သတ်မှတ်ထားတဲ့ အချိန်အတိုင်းအတာ ရပ်တန့်နေစေမှာပဲ ဖြစ်ပါတယ်။ အခုဒီမှာ ၅စက္ကန့် ကြေငြာထားတာ ဖြစ်တဲ့အတွက် Ordering burger... ပြီးတဲ့အခါ ၅စက္ကန့် ရပ်သွားပြီးတော့ အချိန်ပြည့်တော့မှ သူ့နောက်က Got burger! ဆိုတာကို ဆက်ပြီးတော့ run သွားမှာ ဖြစ်ပါတယ်။ ဒီတော့ အခု function ကို အခြား တနေရာက ခေါ်တဲ့အခါ သူ့ထဲမှာ အလုပ်လုပ်တာတွေ မပြီးမချင်း တခြား code တွေကို ဆက်ပြီး အလုပ်လုပ်သွားမှာ မဟုတ်ပါဘူး။ ဒီတော့ သူ့နောက်မှာ ရေးထားတဲ့ 2. Sit down, check phone က စပြီး နောက်ကဟာတွေက burger မရမချင်း အလုပ်လုပ်မှာ မဟုတ်ပါဘူး။
ဒါပေမယ့် အခုလိုချင်တာက burger မှာပြီးတဲ့အခါ သွားထိုင်စောင့်နေပြီး burger ရတော့မှ ရပြီဆိုပြီး သိရတာမျိုး လိုချင်တယ် ဆိုပါစို့။ အဲ့ဒါဆိုရင်တော့ asynchronous programming ဖြစ်တဲ့ Future ကို သုံးပြီးတော့ အခုလိုပဲ ရေးလို့ရပါတယ်။
Future<void> orderFood() async {
print('Ordering burger...');
await Future.delayed(Duration(seconds: 5));
// sleep(Duration(seconds: 5));
print('Got burger!');
}
void main() {
print('1. Go to restaurant');
orderFood();
print('2. Sit down, check phone');
print('3. Reply to messages');
print('4. Browse Instagram');
}
// Output:
// 1. Go to restaurant
// Ordering burger...
// 2. Sit down, check phone
// 3. Reply to messages
// 4. Browse Instagram
// Got burger!ဒီမှာ keyword အသစ် ၃ခုကို တွေ့ရမှာပါ။ async, await, Future ဆိုပြီးတော့ပဲ ဖြစ်ပါတယ်။ အပေါ်မှာ ရေးထားတဲ့ code နဲ့ ၅စက္ကန့် စောင့်တာချင်း တူပေမယ့် တခုကတော့ thread blocking ဖြစ်ပြီး နောက်တခုကတော့ non-blocking ဖြစ်ပါတယ်။ Future ကို အသုံးပြုပြီးတော့ အခုလိုမျိုး non-blocking function တွေကို ရေးနိုင်မှာပဲ ဖြစ်ပါတယ်။
ဒါဆို အသေးစိတ်ကြည့်လိုက်ရအောင်ပါ။ အစဆုံး Future<void> ဆိုတာကတော့ ဒီ function က အလုပ်လုပ်ပြီးရင် ဘာ return ပြန်မလဲလို့ သတ်မှတ်တာပဲ ဖြစ်ပါတယ်။ Future ဆိုတာကတော့ ဒီ function ထဲမှာ အချိန်ယူမယ့်အလုပ်တွေ လုပ်ရဖို့ ရှိမယ်ဆိုတာ သူ့ကိုခေါ်မယ့် နေရာကသိအောင် ပြောပြလိုက်တာနဲ့တူပါတယ်။ ဒီတော့ သူပြီးအောင် စောင့်ချင်ရင်လဲစောင့် မစောင့်ဘဲ သူ့ကို နောက်ကွယ်မှာ အလုပ်လုပ်ဆိုပြီး ခေါ်သွားလို့ ရတဲ့သဘော ဖြစ်ပါတယ်။
void ကတော့ အခု function က အလုပ်လုပ်တာပဲ ကြာဖို့ရှိတာ သူ့ဆီက ဘာမှ return ပြန်ဖို့ မရှိဘူးဆိုတာကို ပြောချင်တဲ့အခါ သုံးတာပဲ ဖြစ်ပါတယ်။ တကယ်လို့ return တခုခု ပြန်ဖို့ ရှိတယ်ဆိုရင်တော့ Future<String>, Future<int> စသဖြင့် return ပြန်မယ့် type ကို ရေးလို့ရပါတယ်။
Future နဲ့ သတ်မှတ်ထားတဲ့ function ဆိုရင် method name ကြေငြာပြီး { မစခင်မှာ async ဆိုတဲ့ keyword ကို ထည့်ပေးဖို့ လိုပါတယ်။ ဒါကတော့ အခု function က asynchronous ဖြစ်မယ်ဆိုတာ method ထဲက သိအောင် ရေးလိုက်တာနဲ့ တူပါတယ်။ Future<void> လိုမျိုး return type မရေးတောင် async ဆိုပြီး ရေးထားလိုက်ရင် function ကို asynchronous အနေနဲ့ run လို့ရမှာပါ။
သူ့ထဲမှာတော့ အချိန်ကြာတဲ့အလုပ် လုပ်စရာရှိတဲ့အခါ သူ့ရှေ့မှာ await ဆိုတဲ့ keyword ကို သုံးပါတယ်။ ဒါကတော့ အခု ခေါ်မယ့် function က အလုပ်လုပ်တာ အချိန်ယူဖို့ရှိတယ်။ ဒီတော့ နောက်ကွယ်မှာ လုပ်စရာရှိတာ သွားလုပ်၊ အဖြေရတဲ့အခါ ပြန်လာခဲ့လို့ ပြောလိုက်တာနဲ့ တူပါတယ်။ သူ့ထဲမှာ ပါတဲ့ အလုပ်တွေကို ဆက်မလုပ်ဘဲ await နောက်မှာပါတဲ့ method က result ပြန်လာတဲ့အထိ စောင့်ပေးနေမှာပဲ ဖြစ်ပါတယ်။ စောင့်တယ်ဆိုလို့ တခြားဟာတွေ ဆက်ပြီး အလုပ်မလုပ်နိုင်အောင် စောင့်နေတာမျိုး မဟုတ်ပါဘူး။ အပေါ်က ပေးထားတဲ့ ဥပမာမှာ ကြည့်မယ်ဆို orderFood လို့ခေါ်လိုက်တဲ့အချိန်မှာ သူ့ထဲက အလုပ်လုပ်မှာတွေကိုပဲ နောက်ကွယ်မှာ သွားပြီးတော့ အလုပ်လုပ်နေတာဖြစ်ပါတယ်။ 2. Sit down, check phone နဲ့သူ့နောက်မှာ print ထားတာတွေက ပြသွားမှာပါ။ နောက်ဆုံး စောင့်ရတဲ့အလုပ်ပြီးပြီ ဆိုတော့မှ Got burger! ဆိုပြီး နောက်ကွယ်က အလုပ်ဆက်လုပ်တာက ပေါ်လာမှာပဲ ဖြစ်ပါတယ်။
Regular Function vs Async Function
// Regular function
String getName() {
return 'Alice';
}
// Async function
Future<String> getNameAsync() async {
return 'Alice';
}
void main() {
String name1 = getName(); // Immediate
Future<String> name2 = getNameAsync(); // Returns Future
print(name1); // Prints: Alice
print(name2); // Prints: Instance of 'Future<String>'
}အပေါ်မှာ ပြထားတဲ့ ဥပမာကို ကြည့်မယ်ဆို async function ကို သုံးတဲ့အခါ await မရှိဘဲ return ပြန်လိုက်တဲ့အခါ result ကတန်းပြီးတော့ String ဖြစ်မသွားဘဲ Instance of 'Future<String>' ဆိုပြီးတော့ ဖြစ်နေမှာပါ။ ဒါကတော့ async function ကို ခေါ်တဲ့အခါ await နဲ့မခေါ်ဘဲ တိုက်ရိုက်ခေါ်လိုက်လို့ Future type ဖြစ်နေတာပါ။
void main() async {
String name1 = getName(); // Immediate
String name2 = await getNameAsync(); // Returns Future<String>
print(name1); // Prints: Alice
print(name2); // Prints: Alice
}
ဒီတော့ async function တိုင်းကို ခေါ်တဲ့အခါ သူ့ထဲက result ကို လိုအပ်တယ်ဆို await ထည့်ကို ထည့်ပေးရမှာပါ။ result ကို မလိုချင်ဘူး၊ ဒါပေမယ့် async function အလုပ်ပြီးတဲ့အထိ စောင့်ချင်တယ်ဆိုလဲ await ထည့်ပေးလို့ရပါတယ်။
void main() async {
print('1. Start');
String name = await fetchName(); // Pause 2s
print('2. Name: $name');
int age = await fetchAge(); // Pause 1s
print('3. Age: $age');
String city = await fetchCity(); // Pause 1s
print('4. City: $city');
print('5. Done!');
}
Future<String> fetchName() async {
await Future.delayed(Duration(seconds: 2));
return Future.value('Alice');
}
Future<int> fetchAge() async {
await Future.delayed(Duration(seconds: 1));
return Future.value(25);
}
Future<String> fetchCity() async {
await Future.delayed(Duration(seconds: 1));
return Future.value('New York');
}
// Total time: 4 seconds (sequential)
// Output:
// 1. Start
// 2. Name: Alice
// 3. Age: 25
// 4. City: New York
// 5. Done!
async function တွေကို တခုထက်မကလဲ စောင့်ပြီးတော့ ခေါ်သွားလို့ရပါတယ်။ အခုလို ရပ်နေတယ်ဆိုတာ program တခုလုံး ရပ်သွားတာ မဟုတ်ပါဘူး။ Dart ရဲ့ event loop ကတော့ ဆက်ပြီး အလုပ်လုပ်နေတာ ဖြစ်တဲ့အတွက် တခြားနေရာက function တွေ ခေါ်မယ်ဆိုရင်လဲ အပြိုင် အလုပ်လုပ်ပေးနေမှာပဲ ဖြစ်ပါတယ်။
Future Patterns
Future Success - Future.value()
class Cache {
final Map<String, dynamic> _data = {'user': 'Alice'};
Future<String> getUser() async {
if (_data.containsKey('user')) {
return Future.value(_data['user']);
}
return _fetchFromNetwork();
}
Future<String> _fetchFromNetwork() async {
await Future.delayed(Duration(seconds: 2));
return 'Bob';
}
}
void main() async {
var cache = Cache();
String user = await cache.getUser();
print('User: $user'); // Instant: Alice
}Async function ကနေ result တွေပြန်ချင်တဲ့အခါ await မပါဘူးဆို Future.value(...) ဆိုပြီး သူ့ထဲမှာ ကြေငြာထားတဲ့ data type ကို ထည့်ပြီး return ပြန်လို့ရပါတယ်။ Async function တွေမှာ return ပြန်ချင်တယ်ဆို ဒါနဲ့သုံးဖို့ အကြံပြုပါတယ်။ Future.value(...) နဲ့ value ကို wrap မလုပ်ဘဲ ပြန်တာလဲ ရပေမယ့် async keyword မေ့ကျန်ခဲ့ရင် runtime မှာ error ပြနေပါလိမ့်မယ်။
Future Failure - Future.error()
Future<void> withdraw(double amount) {
if (amount <= 0) {
return Future.error('Amount must be positive');
}
if (amount > 1000) {
return Future.error('Daily limit exceeded');
}
return _processWithdrawal(amount);
}
Future<void> _processWithdrawal(double amount) async {
await Future.delayed(Duration(seconds: 1));
print('Withdrew: \$$amount');
}
void main() async {
try {
await withdraw(-50);
} catch (e) {
print('Error: $e'); // Error: Amount must be positive
}
}ဒါကိုတော့ async function ကနေ error အဖြစ်နဲ့ return ပြန်ချင်တဲ့အခါ အသုံးပြုနိုင်ပါတယ်။ သူ့ထဲမှာ Object/Exception ကြိုက်တာ ထည့်ပေးလို့ရပါတယ်။ error throw လုပ်တာနဲ့ တူပါတယ်။ အပေါ်မှာ ပြောခဲ့သလိုပဲ Future.error(...) လို့သုံးခြင်းက async keyword မေ့ကျန်ခဲ့လို့ runtime မှာ error မတက်အောင် ကာကွယ်ပေးပါတယ်။ Future မှာလဲ error တွေ ဖြစ်နိုင်တယ်/ဖြစ်လာတယ်ဆိုရင် try/catch နဲ့ အသုံးပြုလို့ရပါတယ်။
[CREATED]
↓
⏳ UNCOMPLETED
(Waiting...)
↓
┌────┴────┐
↓ ↓
✅ SUCCESS ❌ ERROR
(Value) (Exception)Simulated Delay - Future.delayed()
Future<String> mockApiCall() {
return Future.delayed(
Duration(seconds: 2),
() => 'User data loaded',
);
}
Future<int> randomDelay() {
return Future.delayed(
Duration(milliseconds: 500),
() => 42,
);
}
void main() async {
String data = await mockApiCall();
print(data); // After 2s: User data loaded
int number = await randomDelay();
print(number); // After 0.5s: 42
}ဒါကတော့ အပေါ်မှာ မြင်ဖူးပြီးသားပါ။ တကယ်စောင့်ရတာမျိုးမဟုတ်ဘဲ စောင့်သယောင် ဖန်တီးချင်တဲ့အခါ သူ့ကို အသုံးပြုပါတယ်။ သူကနေပြီး စောင့်တဲ့အချိန် ပြည့်သွားတဲ့အခါ return ပြန်ချင်တယ်ဆိုရင် callback အနေနဲ့ ထည့်ပြီး ရေးပေးလို့ရပါတယ်။
Parallel Execution - Future.wait()
Future<String> fetchName() async {
await Future.delayed(Duration(seconds: 2));
return Future.value('Alice');
}
Future<int> fetchAge() async {
await Future.delayed(Duration(seconds: 3));
return Future.value(25);
}
Future<String> fetchCity() async {
await Future.delayed(Duration(seconds: 4));
return Future.value('New York');
}
void main() async {
print('Sequential (SLOW):');
var start1 = DateTime.now();
// ❌ SEQUENTIAL - 9 seconds
String name = await fetchName();
int age = await fetchAge();
String city = await fetchCity();
var duration1 = DateTime.now().difference(start1);
print('Time: ${duration1.inSeconds}s'); // 6 seconds
print('\nParallel (FAST):');
var start2 = DateTime.now();
// ✅ PARALLEL - 4 seconds
var results = await Future.wait([fetchName(), fetchAge(), fetchCity()]);
String name2 = results[0] as String;
int age2 = results[1] as int;
String city2 = results[2] as String;
var duration2 = DateTime.now().difference(start2);
print('Time: ${duration2.inSeconds}s'); // 4 seconds
print('Speed up: ${duration1.inSeconds / duration2.inSeconds}x faster!');
}တကယ်လို့ async function တခုထက်ပိုပြီး ခေါ်စရာရှိပြီး အကုန်လုံးကို တခုပြီးတခု run ဖို့ မလိုတဲ့အခါ Future.wait(...) ကို အသုံးပြုနိုင်ပါတယ်။ တခုရှိတာက Future.wait(...) မှာ သူ့ထဲက future တွေ အကုန်ပြီးမှ return ပြန်မှာ ဖြစ်တဲ့အတွက် အကြာဆုံးရဲ့ အချိန်အတိုင်း ဖြစ်သွားမှာပဲ ဖြစ်ပါတယ်။ အပေါ်မှာ ကြည့်မယ်ဆို fetchName() method က ၂စက္ကန့်နဲ့ ပြီးရမှာ ဖြစ်ပေမယ့် Future.wait(...) ထဲကိုထည့်ပြီး ခေါ်လိုက်တဲ့အခါ အကြာဆုံး method ဖြစ်တဲ့ fetchCity() ရဲ့ ကြာချိန်အတိုင်း ၄စက္ကန့် ဖြစ်သွားပါတယ်။
First to Complete - Future.any()
Future<String> server1() async {
await Future.delayed(Duration(seconds: 3));
return Future.value('Data from Server 1');
}
Future<String> server2() async {
await Future.delayed(Duration(seconds: 1));
return Future.value('Data from Server 2');
}
Future<String> server3() async {
await Future.delayed(Duration(seconds: 2));
return Future.value('Data from Server 3');
}
void main() async {
print('Requesting from 3 servers...');
// Returns whichever completes first
String data = await Future.any([server1(), server2(), server3()]);
print('Fastest: $data'); // Data from Server 2
}
ဒါကိိုတော့ data ယူလို့ရမယ့်နေရာက တနေရာထက်များပြီး တနေရာက ရပြီဆိုတာနဲ့ တခြားနေရာတွေက ဘာပြန်လာမလဲဆိုတာ စောင့်စရာ မလိုဘူးဆိုတဲ့ အခြေအနေမှာ သုံးပါတယ်။ အပေါ်က ပြထားတဲ့ ဥပမာမှာတော့ Server 2 က response က အမြန်ဆုံးဖြစ်တဲ့အတွက် နောက်ဆုံး ထွက်လာတဲ့ value က သူ့ဆီက ဖြစ်နေမှာပါ။
Manual Control - Completer
import 'dart:async';
class SmartConnection {
final Completer<String> _completer = Completer();
SmartConnection() {
_attemptConnection();
}
Future<String> get connected => _completer.future;
void _attemptConnection() {
Future.delayed(Duration(seconds: 1), () {
// Simulate random success/failure
if (DateTime.now().second % 2 == 0) {
_completer.complete('Success!');
} else {
_completer.completeError('Connection failed');
}
});
}
}
void main() async {
try {
var conn = SmartConnection();
String result = await conn.connected;
print(result);
} catch (e) {
print('Error: $e');
}
}ဒါကိုတော့ တခုခုကို စောင့်ဖို့လိုပြီး ဘယ်လောက်ကြာမလဲ သေချာ မသိတဲ့အချိန်မှာ အသုံးပြုနိုင်ပါတယ်။ သူ့ရဲ့ async loop ကို အပြီးသတ်ဖို့ အပြင်ကို စောင့်ဖို့မလိုဘဲ ကိုယ်တိုင် ရပ်လို့ရနိုင်တဲ့ async function မျိုးပဲ ဖြစ်ပါတယ်။ ဆိုလိုတာကတော့ API တွေ ခေါ်တဲ့အခါ ပြီးတယ်လို့ သတ်မှတ်တာက remote server က ဖြစ်ပြီး data/error တခုခုဝင်လာတဲ့အခါ ပြီးတယ်လို့ သတ်မှတ်ပါတယ်။ Completer ကတော့ လက်ရှိရေးနေသူကိုယ်တိုင် ပြီးချင်တဲ့အချိန်မှာ ပြီးလိုက်လို့ ရတာမျိုးပဲ ဖြစ်ပါတယ်။
အခုလို Future ရဲ့ ရေးပုံရေးနည်းတွေကို သိသွားပြီဆိုရင်တော့ Flutter app တွေ ရေးတဲ့အခါ မျိုးစုံသော နေရာတွေက data တွေကို ကောင်းကောင်း ကိုင်တွယ်သွားနိုင်မှာပဲ ဖြစ်ပါတယ်။
ဒီနေ့ Day 12 အတွက်ကတော့ ဒီလောက်ပဲ ဖြစ်ပါတယ်။ အဆုံးထိ ဖတ်ပေးတဲ့အတွက် အများကြီး ကျေးဇူးတင်ပါတယ်။ နားမလည်တာတွေ အဆင်မပြေတာတွေ ရှိခဲ့ရင်လဲ အောက်မှာပေးထားတဲ့ discord server ထဲမှာ လာရောက်ဆွေးနွေးနိုင်ပါတယ်။ နောက်နေ့တွေမှာလဲ ဆက်လက်ပြီး sharing လုပ်သွားပေးသွားမှာ ဖြစ်တဲ့အတွက် subscribe လုပ်ထားဖို့ ဖိတ်ခေါ်ပါတယ်။
- Youtube: https://www.youtube.com/@arkarmintun
- Newsletter: https://arkar.dev/
- Discord: https://discord.gg/3xUJ6k6dkH