dart-async-programming
dart-async-programming
Goal
Implements robust, idiomatic asynchronous Dart code using Future and Stream APIs. Manages concurrency, stream processing, and error handling while adhering to strict Dart async patterns, memory safety guidelines, and optimal execution flow.
Decision Logic
When determining the appropriate asynchronous pattern, evaluate the following decision tree:
- Single Asynchronous Operation: Use
Future<T>withasyncandawait. - Multiple Independent Operations: Use
Future.waitto execute concurrently. - Sequence of Asynchronous Events: Use
Stream<T>.- Finite/Sequential Data (e.g., File I/O, Network Responses): Consume using
await for. - Infinite/Event-Driven Data (e.g., UI Events): Use
listen()on a Broadcast Stream.
- Finite/Sequential Data (e.g., File I/O, Network Responses): Consume using
- Custom Stream Generation:
- Simple Generation: Use
async*andyield. - Complex State/Event Management: Use
StreamController.
- Simple Generation: Use
Instructions
-
Implement Single Futures with Error Handling Always use
async/awaitsyntax instead of.then(). Wrap operations intry-catchblocks to handle exceptions gracefully.Future<String> fetchUserData(String userId) async { try { final result = await api.getUser(userId); return result.name; } catch (e, stackTrace) { // Handle specific errors or rethrow logError('Failed to fetch user: $e', stackTrace); throw UserFetchException(e.toString()); } } -
Execute Independent Futures Concurrently When multiple asynchronous operations do not depend on each other, initiate them concurrently using
Future.wait.Future<UserProfile> loadCompleteProfile(String userId) async { try { final results = await Future.wait([ api.getUser(userId), api.getUserPreferences(userId), api.getUserHistory(userId), ]); return UserProfile( user: results[0] as User, preferences: results[1] as Preferences, history: results[2] as History, ); } catch (e) { throw ProfileLoadException('Failed to load profile components: $e'); } } -
Consume Streams Sequentially Use
await forto consume streams when you need to process events sequentially and wait for the stream to close.Future<int> calculateTotal(Stream<int> numberStream) async { int total = 0; try { await for (final number in numberStream) { total += number; } } catch (e) { logError('Stream processing failed: $e'); return -1; } return total; } -
Generate Streams using Generators For straightforward sequential event generation, use
async*andyield.Stream<int> generateCountdown(int from) async* { for (int i = from; i >= 0; i--) { await Future.delayed(const Duration(seconds: 1)); yield i; } } -
Manage Complex Streams with StreamController When manually controlling stream state, instantiate a
StreamController. You MUST ensure the controller is closed when no longer needed to prevent memory leaks.class DataManager { final StreamController<DataEvent> _controller = StreamController<DataEvent>.broadcast(); Stream<DataEvent> get dataStream => _controller.stream; void addData(DataEvent event) { if (!_controller.isClosed) { _controller.add(event); } } void dispose() { _controller.close(); } } -
Apply Stream Transformations and Timeouts Use built-in stream methods to handle errors, timeouts, and transformations before consumption.
Stream<String> processNetworkStream(Stream<List<int>> byteStream) async* { final safeStream = byteStream .handleError((e) => logError('Network error: $e')) .timeout( const Duration(seconds: 5), onTimeout: (sink) { sink.addError(TimeoutException('Stream timed out')); sink.close(); }, ) .transform(utf8.decoder) .transform(const LineSplitter()); await for (final line in safeStream) { if (line.isNotEmpty) yield line; } } -
Context Checkpoint STOP AND ASK THE USER: If the user requests stream implementation but does not specify if the stream will have multiple listeners (e.g., UI state) or a single listener (e.g., file reading), ask them to clarify before implementing
StreamControllerorStreamController.broadcast(). -
Validate-and-Fix After generating asynchronous code, perform a validation pass:
- Check: Are there any raw
.then()or.catchError()chains? Fix: Convert toasync/awaitandtry-catch. - Check: Are multiple independent
awaitcalls made sequentially? Fix: Combine them usingFuture.wait. - Check: Is a
StreamControllerinstantiated without a correspondingclose()method in a disposal lifecycle? Fix: Add theclose()call.
- Check: Are there any raw
Constraints
- DO use
async/awaitinstead of raw.then()calls for better readability. - DO wrap asynchronous calls in
try-catchto handle errors gracefully. - PREFER
Future.waitto initiate multiple independent futures concurrently. - PREFER
await foroverforEachorlistenwhen consuming streams sequentially. - DO use
StreamControllerwith aclose()call to prevent memory leaks. - DO NOT use
await forfor UI event listeners, as UI frameworks send endless streams of events which will block execution indefinitely. - Related Skills:
dart-idiomatic-usage,dart-concurrency-isolates.