⏳

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

1

Future + async/await β€” await inside async functions for sync-like readable code

2

Future.wait() β€” run multiple async tasks in parallel, wait for all to complete

3

Stream + async*/yield β€” generator for continuous events, ideal for real-time data

4

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

Use Cases

Displaying REST API results on screen (FutureBuilder) Real-time data like chat, stock prices, location tracking (StreamBuilder)