Dart Async Programming
Future, async/await, Stream, FutureBuilder
Future handles single async results, Stream handles continuous async events. async/await writes async code cleanly like sync, with Future.wait() for parallelism. In Flutter, FutureBuilder and StreamBuilder declaratively manage loading/error/success states.
Future β λμ€μ μλ£λλ κ°
Futureλ λΉλκΈ° μμ
μ κ²°κ³Όλ₯Ό λνλ΄λ κ°μ²΄μ
λλ€. thenμΌλ‘ μ±κ³΅, catchErrorλ‘ μ€ν¨, whenCompleteλ‘ μλ£λ₯Ό μ²λ¦¬ν©λλ€.
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 3), () {
return 'μλ²μμ λ°μ λ°μ΄ν°';
});
}
void main() {
print('μμ
μμ');
fetchData().then((data) {
print('λ°μ΄ν°: $data');
}).catchError((error) {
print('μ€λ₯ λ°μ: $error');
}).whenComplete(() {
print('μμ
μλ£');
});
print('λ€μ μμ
μ§ν');
}
Future μμ± λ°©λ²
| λ©μλ | μ€λͺ |
|---|---|
| Future.value() | μ¦μ μλ£λλ Future |
| Future.delayed() | μ§μ μκ° ν μλ£ |
| Future.error() | μ€λ₯λ‘ μλ£λλ Future |
| Completer | 볡μ‘ν λΉλκΈ° λ‘μ§ μ§μ μ μ΄ |
// Future.value β μ¦μ μλ£
Future<String> getFuture() {
return Future.value('μ¦μ μ¬μ© κ°λ₯ν κ°');
}
// Future.delayed β μ§μ μκ° ν μλ£
Future<String> getDelayedFuture() {
return Future.delayed(Duration(seconds: 2), () {
return '2μ΄ ν μ¬μ© κ°λ₯ν κ°';
});
}
// Future.error β μ€λ₯λ‘ μλ£
Future<String> getErrorFuture() {
return Future.error('μ€λ₯ λ°μ');
}
// Completer β μλ μ μ΄
import 'dart:async';
Future<String> complexOperation() {
final completer = Completer<String>();
Timer(Duration(seconds: 2), () {
if (DateTime.now().second % 2 == 0) {
completer.complete('μ±κ³΅!');
} else {
completer.completeError('μ€ν¨!');
}
});
return completer.future;
}
Future 체μ΄λ
μ¬λ¬ λΉλκΈ° μμ
μ thenμΌλ‘ μ°κ²°νμ¬ μμ°¨μ μΌλ‘ μ€νν μ μμ΅λλ€.
void main() {
fetchUserId()
.then((id) => fetchUserData(id))
.then((userData) => saveUserData(userData))
.then((_) => print('λͺ¨λ μμ
μλ£'))
.catchError((error) => print('μ€λ₯ λ°μ: $error'));
}
Future<String> fetchUserId() => Future.value('user123');
Future<Map<String, dynamic>> fetchUserData(String id) =>
Future.value({'id': id, 'name': 'νκΈΈλ', 'email': 'hong@example.com'});
Future<void> saveUserData(Map<String, dynamic> userData) =>
Future.value(print('λ°μ΄ν° μ μ₯λ¨: $userData'));
async/await β λκΈ°μμ²λΌ μ½νλ λΉλκΈ° μ½λ
async ν¨μλ νμ Futureλ₯Ό λ°ννλ©°, awaitλ‘ κ²°κ³Όλ₯Ό κΈ°λ€λ¦½λλ€. try-catch-finallyλ‘ μλ¬ μ²λ¦¬κ° κ°λ₯ν©λλ€.
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'μλ²μμ λ°μ λ°μ΄ν°';
}
void main() async {
print('μμ
μμ');
try {
String data = await fetchData();
print('λ°μ΄ν°: $data');
} catch (e) {
print('μ€λ₯ λ°μ: $e');
} finally {
print('μμ
μλ£');
}
print('λ€μ μμ
μ§ν');
}
μμ°¨ μ²λ¦¬ vs λ³λ ¬ μ²λ¦¬
μμ°¨ μ²λ¦¬λ awaitλ₯Ό μ°μμΌλ‘, λ³λ ¬ μ²λ¦¬λ Future.wait()λ‘ λμμ μ€νν©λλ€.
// μμ°¨ μ²λ¦¬ β μ΄ 6μ΄ (2+3+1)
Future<void> sequentialTasks() async {
final startTime = DateTime.now();
final result1 = await task1(); // 2μ΄
final result2 = await task2(); // 3μ΄
final result3 = await task3(); // 1μ΄
print('μμ μκ°: ${DateTime.now().difference(startTime).inSeconds}μ΄');
}
// λ³λ ¬ μ²λ¦¬ β μ΄ 3μ΄ (κ°μ₯ λλ¦° μμ
κΈ°μ€)
Future<void> parallelTasks() async {
final startTime = DateTime.now();
final results = await Future.wait([
task1(), // 2μ΄
task2(), // 3μ΄
task3(), // 1μ΄
]);
print('μμ μκ°: ${DateTime.now().difference(startTime).inSeconds}μ΄');
}
Future<String> task1() => Future.delayed(Duration(seconds: 2), () => 'μμ
1 κ²°κ³Ό');
Future<String> task2() => Future.delayed(Duration(seconds: 3), () => 'μμ
2 κ²°κ³Ό');
Future<String> task3() => Future.delayed(Duration(seconds: 1), () => 'μμ
3 κ²°κ³Ό');
Future API: wait, any, forEach
// Future.wait β λͺ¨λ μλ£λ λκΉμ§ λκΈ°
Future<void> waitExample() async {
final results = await Future.wait([
Future.delayed(Duration(seconds: 1), () => 'κ²°κ³Ό1'),
Future.delayed(Duration(seconds: 2), () => 'κ²°κ³Ό2'),
Future.delayed(Duration(seconds: 3), () => 'κ²°κ³Ό3'),
]);
print(results); // [κ²°κ³Ό1, κ²°κ³Ό2, κ²°κ³Ό3]
}
// Future.any β κ°μ₯ λ¨Όμ μλ£λ νλλ§
Future<void> anyExample() async {
final result = await Future.any([
Future.delayed(Duration(seconds: 3), () => 'λλ¦° μμ
'),
Future.delayed(Duration(seconds: 1), () => 'λΉ λ₯Έ μμ
'),
Future.delayed(Duration(seconds: 2), () => 'μ€κ° μμ
'),
]);
print(result); // 'λΉ λ₯Έ μμ
'
}
// Future.forEach β μμ°¨μ μΌλ‘ νλμ© μ²λ¦¬
Future<void> forEachExample() async {
final items = [1, 2, 3, 4, 5];
await Future.forEach(items, (int item) async {
await Future.delayed(Duration(milliseconds: 500));
print('μ²λ¦¬ μ€: $item');
});
print('λͺ¨λ νλͺ© μ²λ¦¬ μλ£');
}
Stream β μ°μμ μΈ λΉλκΈ° μ΄λ²€νΈ
Streamμ μκ°μ λ°λΌ μ¬λ¬ κ°μ λΉλκΈ°μ μΌλ‘ μ 곡ν©λλ€. async*μ yieldλ‘ μ λλ μ΄ν°λ₯Ό λ§λ€ μ μμ΅λλ€.
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 μ¬μ©
await for (final count in countStream(5)) {
print(count);
}
// listen μ¬μ©
countStream(5).listen(
(data) => print(data),
onError: (error) => print('μ€λ₯: $error'),
onDone: () => print('μ€νΈλ¦Ό μλ£'),
);
}
StreamController & Stream μμ± λ°©λ²
import 'dart:async';
// StreamController β μΈλ°ν μ μ΄
Stream<int> getControllerStream() {
final controller = StreamController<int>();
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick <= 5) {
controller.add(timer.tick);
} else {
controller.close();
timer.cancel();
}
});
return controller.stream;
}
// Stream.fromIterable β 컬λ μ
μμ μμ±
Stream<int> getIterableStream() {
return Stream.fromIterable([1, 2, 3, 4, 5]);
}
// Stream.periodic β μ£ΌκΈ°μ μ΄λ²€νΈ
Stream<int> getPeriodicStream() {
return Stream.periodic(Duration(seconds: 1), (count) => count + 1)
.take(5);
}
Stream λ³ν (map, where, take)
void streamTransformations() async {
final stream = Stream.fromIterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
final doubled = stream.map((value) => value * 2);
final evenOnly = doubled.where((value) => value % 2 == 0);
final limited = evenOnly.take(3);
await for (final value in limited) {
print(value); // 2, 4, 6
}
}
Broadcast Stream β μ¬λ¬ ꡬλ μ
μΌλ° Streamμ ν λ²λ§ ꡬλ
κ°λ₯νμ§λ§, StreamController.broadcast()λ‘ μ¬λ¬ ꡬλ
μλ₯Ό μ§μν©λλ€.
void broadcastStreamExample() {
final controller = StreamController<int>.broadcast();
final subscription1 = controller.stream.listen(
(data) => print('ꡬλ
μ 1: $data'),
onDone: () => print('ꡬλ
μ 1: μλ£'),
);
final subscription2 = controller.stream.listen(
(data) => print('ꡬλ
μ 2: $data'),
onDone: () => print('ꡬλ
μ 2: μλ£'),
);
controller.add(1);
controller.add(2);
controller.add(3);
subscription1.cancel(); // ꡬλ
μ 1 ν΄μ
controller.add(4); // ꡬλ
μ 2λ§ μμ
controller.add(5);
controller.close();
}
Stream ꡬλ κ΄λ¦¬ (λ©λͺ¨λ¦¬ λμ λ°©μ§)
μ£Όμ: Stream ꡬλ ν΄μ νμ!
ꡬλ
μ ν΄μ νμ§ μμΌλ©΄ λ©λͺ¨λ¦¬ λμκ° λ°μν©λλ€. dispose()μμ λ°λμ cancel()μ νΈμΆνμΈμ.
class DataService {
StreamSubscription<int>? _subscription;
void startListening() {
_subscription?.cancel(); // κΈ°μ‘΄ ꡬλ
ν΄μ
_subscription = getPeriodicStream().listen(
(data) => print('λ°μ λ°μ΄ν°: $data'),
onDone: () => print('μ€νΈλ¦Ό μλ£'),
);
}
void stopListening() {
_subscription?.cancel();
_subscription = null;
}
void dispose() {
stopListening();
}
}
FutureBuilder β λ¨μΌ λΉλκΈ° κ²°κ³Όλ₯Ό μμ ―μΌλ‘
connectionStateλ‘ λ‘λ©/μλ¬/μ±κ³΅ μνλ₯Ό μλ κ΄λ¦¬ν©λλ€.
FutureBuilder<String>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('μ€λ₯ λ°μ: ${snapshot.error}');
} else if (snapshot.hasData) {
return Text('λ°μ΄ν°: ${snapshot.data}');
} else {
return Text('λ°μ΄ν° μμ');
}
},
)
StreamBuilder β μ€μκ° λ°μ΄ν°λ₯Ό μμ ―μΌλ‘
StreamBuilder<int>(
stream: countdownStream(10),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return Text('μΉ΄μ΄νΈλ€μ΄: ${snapshot.data}');
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('μΉ΄μ΄νΈλ€μ΄ μλ£!');
} else {
return CircularProgressIndicator();
}
},
)
μ€μ μμ : FutureBuilderλ‘ μ¬μ©μ λͺ©λ‘ νμ
Future<List<User>> fetchUsers() async {
final response = await http.get(
Uri.parse('https://api.example.com/users'),
);
if (response.statusCode == 200) {
final List<dynamic> data = jsonDecode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
class UserListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('μ¬μ©μ λͺ©λ‘')),
body: FutureBuilder<List<User>>(
future: fetchUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('μ€λ₯: ${snapshot.error}'));
} else if (snapshot.hasData) {
final users = snapshot.data!;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
);
} else {
return Center(child: Text('μ¬μ©μκ° μμ΅λλ€'));
}
},
),
);
}
}
νμμμ μ²λ¦¬
Future<String> fetchWithTimeout() {
return fetchData().timeout(
Duration(seconds: 5),
onTimeout: () => throw TimeoutException('μμ² μκ° μ΄κ³Ό'),
);
}
compute() β CPU μ§μ½ μμ 격리
λ¬΄κ±°μ΄ μ°μ°μ compute()λ‘ λ³λ Isolateμμ μ€ννμ¬ UI μ€λ λλ₯Ό 보νΈν©λλ€.
Future<List<ComplexData>> processLargeDataSet(
List<RawData> rawData,
) {
return compute(processDataInBackground, rawData);
}
List<ComplexData> processDataInBackground(
List<RawData> rawData,
) {
return rawData.map((raw) => ComplexData.process(raw)).toList();
}
UI νΌλλ°± ν¨ν΄ (λ‘λ©/μ±κ³΅/μ€ν¨)
Future<void> saveData() async {
setState(() => _isLoading = true);
try {
await uploadData();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('λ°μ΄ν°κ° μ±κ³΅μ μΌλ‘ μ μ₯λμμ΅λλ€')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('μ μ₯ μ€ν¨: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}
λͺ¨λ² μ¬λ‘ μμ½
-
✓
try-catch νμ β λΉλκΈ° μμ μλ νμ μλ¬ μ²λ¦¬λ₯Ό μΆκ°νμΈμ
-
✓
Stream ꡬλ ν΄μ β dispose()μμ λ°λμ cancel()μ νΈμΆνμ¬ λ©λͺ¨λ¦¬ λμ λ°©μ§
-
✓
λ³λ ¬ μ²λ¦¬ νμ© β λ 립μ μΈ μμ μ Future.wait()λ‘ λμ μ€ννμ¬ μ±λ₯ ν₯μ
-
✓
compute() μ¬μ© β CPU μ§μ½μ μ°μ°μ λ³λ Isolateμμ μ€ννμ¬ UI νλ μ λλ‘ λ°©μ§
-
✓
νμμμ μ€μ β λ€νΈμν¬ μμ²μλ .timeout()μ μΆκ°νμ¬ λ¬΄ν λκΈ° λ°©μ§
Implementation Steps
Future + async/await β await inside async functions for sync-like readable code
Future.wait() β run multiple async tasks in parallel, wait for all to complete
Stream + async*/yield β generator for continuous events, ideal for real-time data
FutureBuilder/StreamBuilder β display async data as widgets in Flutter, auto-manage loading/error/success
Pros
- ✓ Clean async code without callback hell using async/await
- ✓ Declarative async state management with FutureBuilder/StreamBuilder
Cons
- ✗ Memory leaks if Stream subscriptions are not cancelled
- ✗ Missing error handling can crash the app