Documentation Index Fetch the complete documentation index at: https://mintlify.com/dart-lang/sdk/llms.txt
Use this file to discover all available pages before exploring further.
The dart:async library provides support for asynchronous programming with classes such as Future and Stream. These are the fundamental building blocks of asynchronous programming in Dart.
Overview
To use this library in your code:
The dart:async library includes:
Future - Single asynchronous computation result
Stream - Sequence of asynchronous events
async/await - Language-level async support
Timer - Scheduled execution
Zone - Execution contexts for async code
Future
Overview
Represents the result of an asynchronous computation. A Future object represents a computation whose return value might not yet be available.
Creating Futures
// Using async function (recommended)
Future < String > fetchUserName () async {
// Simulate network delay
await Future . delayed ( Duration (seconds : 2 ));
return 'John Doe' ;
}
// Using Future constructor
var future = Future < int >(() {
return expensiveComputation ();
});
// Immediate values
var immediate = Future . value ( 42 );
var error = Future . error ( Exception ( 'Failed' ));
// Delayed execution
var delayed = Future . delayed (
Duration (seconds : 2 ),
() => 'Done!' ,
);
Using async/await
// Basic async function
Future < bool > fileContains ( String path, String needle) async {
var haystack = await File (path). readAsString ();
return haystack. contains (needle);
}
// Sequential operations
Future < void > processUser () async {
var user = await fetchUser ();
var profile = await fetchProfile (user.id);
var posts = await fetchPosts (user.id);
print ( 'User: $ user , Posts: ${ posts . length } ' );
}
// Parallel operations with Future.wait
Future < void > loadDashboard () async {
var results = await Future . wait ([
fetchUser (),
fetchPosts (),
fetchComments (),
]);
var user = results[ 0 ];
var posts = results[ 1 ];
var comments = results[ 2 ];
}
// Error handling
Future < void > handleErrors () async {
try {
var data = await fetchData ();
process (data);
} on NetworkException catch (e) {
print ( 'Network error: $ e ' );
} catch (e, stackTrace) {
print ( 'Error: $ e ' );
print ( 'Stack trace: $ stackTrace ' );
} finally {
cleanup ();
}
}
Future Methods
then<R>(FutureOr<R> onValue(T value))
Registers a callback to be called when the future completes successfully.
catchError(Function onError)
Handles errors from the future.
whenComplete(FutureOr action())
Registers a callback to be called when the future completes, regardless of success or failure.
// Callback-based (older style)
fetchUser ()
. then ((user) => print ( 'User: $ user ' ))
. catchError ((error) => print ( 'Error: $ error ' ))
. whenComplete (() => print ( 'Done' ));
// Chaining futures
fetchUser ()
. then ((user) => fetchProfile (user.id))
. then ((profile) => print ( 'Profile: $ profile ' ));
// Timeout
var result = await fetchData (). timeout (
Duration (seconds : 5 ),
onTimeout : () => throw TimeoutException ( 'Too slow' ),
);
Key static methods:
Future.wait(List<Future>) - Waits for multiple futures
Future.any(Iterable<Future>) - Returns first completed future
Future.delayed(Duration) - Creates delayed future
Future.value(T) - Creates completed future
Future.error(Object) - Creates failed future
Future Patterns
// Fire and forget (not recommended)
fetchData (); // No await, result ignored
// Wait for completion
await fetchData ();
// Store future for later
var future = fetchData ();
// ... do other work ...
var result = await future;
// Race condition - first to complete wins
var winner = await Future . any ([
fetchFromCache (),
fetchFromNetwork (),
]);
// All or nothing
try {
var results = await Future . wait ([
operation1 (),
operation2 (),
operation3 (),
]);
} catch (e) {
// If any fails, all fail
}
Stream
Overview
A source of asynchronous data events. Provides a way to receive a sequence of events over time.
Creating Streams
// Using async* generator
Stream < int > countStream ( int max) async * {
for ( int i = 1 ; i <= max; i ++ ) {
await Future . delayed ( Duration (seconds : 1 ));
yield i;
}
}
// From iterable
var stream = Stream . fromIterable ([ 1 , 2 , 3 , 4 , 5 ]);
// From future
var futureStream = Stream . fromFuture ( fetchData ());
// Periodic stream
var periodic = Stream . periodic (
Duration (seconds : 1 ),
(count) => count,
). take ( 10 );
// Using StreamController
var controller = StreamController < String >();
controller.sink. add ( 'Hello' );
controller.sink. add ( 'World' );
var stream = controller.stream;
Consuming Streams
Stream Consumption Patterns
// Using await for loop (recommended)
Stream < int > stream = countStream ( 5 );
await for ( var value in stream) {
print ( 'Received: $ value ' );
}
// Using listen
var subscription = stream. listen (
(data) {
print ( 'Data: $ data ' );
},
onError : (error) {
print ( 'Error: $ error ' );
},
onDone : () {
print ( 'Stream closed' );
},
cancelOnError : false ,
);
// Pause and resume
subscription. pause ();
// ... later ...
subscription. resume ();
// Cancel subscription
await subscription. cancel ();
// Using forEach
await stream. forEach ((data) {
print ( 'Data: $ data ' );
});
// Convert to future
var lastValue = await stream.last;
var firstValue = await stream.first;
var allValues = await stream. toList ();
Stream Transformation Methods
Stream Types
Single-subscription streams can only be listened to once. Examples: file I/O, HTTP requests.Broadcast streams can be listened to multiple times. Examples: UI events, WebSocket messages.
// Convert to broadcast stream
var broadcast = stream. asBroadcastStream ();
// Multiple listeners
broadcast. listen ((data) => print ( 'Listener 1: $ data ' ));
broadcast. listen ((data) => print ( 'Listener 2: $ data ' ));
// Check stream type
if (stream.isBroadcast) {
print ( 'This is a broadcast stream' );
}
StreamController
Creates and controls a stream. Provides a way to send data, error, and done events to a stream.
// Create controller
var controller = StreamController < int >();
// Add events
controller.sink. add ( 1 );
controller.sink. add ( 2 );
controller.sink. addError ( Exception ( 'Error' ));
// Listen to stream
controller.stream. listen (
(data) => print (data),
onError : (error) => print ( 'Error: $ error ' ),
onDone : () => print ( 'Done' ),
);
// Close when done
await controller. close ();
// Broadcast controller
var broadcastController = StreamController < int >. broadcast ();
Timer
Schedules callbacks for future execution. Can be one-time or periodic.
// One-time timer
Timer ( Duration (seconds : 5 ), () {
print ( '5 seconds elapsed' );
});
// Periodic timer
var count = 0 ;
var timer = Timer . periodic ( Duration (seconds : 1 ), (timer) {
count ++ ;
print ( 'Tick $ count ' );
if (count >= 10 ) {
timer. cancel ();
}
});
// Cancel timer
timer. cancel ();
// Check if timer is active
if (timer.isActive) {
print ( 'Timer is running' );
}
Zone
An execution context for asynchronous code. Zones can intercept errors, schedule tasks, and store values.
import 'dart:async' ;
// Run code in a zone
runZoned (() {
// This code runs in a custom zone
throw Exception ( 'Error in zone' );
}, onError : (error, stackTrace) {
print ( 'Caught error: $ error ' );
});
// Zone with custom error handling
runZonedGuarded (() async {
// App code here
await runApp ();
}, (error, stackTrace) {
// Global error handler
logError (error, stackTrace);
});
// Zone values
var zone = Zone .current. fork (
zoneValues : { 'requestId' : '12345' },
);
zone. run (() {
var requestId = Zone .current[ 'requestId' ];
print ( 'Request ID: $ requestId ' );
});
Microtasks
scheduleMicrotask(void callback())
Schedules a callback to run as a microtask. Microtasks run before the next event in the event queue.
print ( '1' );
scheduleMicrotask (() => print ( '3' ));
print ( '2' );
// Output: 1, 2, 3
// Event queue vs microtask queue
print ( 'A' );
Future (() => print ( 'C' )); // Event queue
scheduleMicrotask (() => print ( 'B' )); // Microtask queue
print ( 'A done' );
// Output: A, A done, B, C
Completer
Manually controls a Future. Use when you need to complete a Future from outside the async operation.
Completer < String > completer = Completer ();
// Return the future
Future < String > getResult () {
return completer.future;
}
// Complete it later
void handleData ( String data) {
if ( ! completer.isCompleted) {
completer. complete (data);
}
}
void handleError ( Object error) {
if ( ! completer.isCompleted) {
completer. completeError (error);
}
}
Best Practices
Always handle errors in async code with try-catch or catchError
Don’t mix async/await with then/catchError callbacks
Cancel streams when done to prevent memory leaks
Use await instead of .then() for better readability
Be careful with unawaited futures - they can cause silent failures
Prefer async/await over callbacks for better code readability and error handling. Use Streams for multiple values over time, and Futures for single asynchronous results.
Common Patterns
// Retry logic
Future < T > retry < T >( Future < T > Function () operation, { int maxAttempts = 3 }) async {
for ( var attempt = 1 ; attempt <= maxAttempts; attempt ++ ) {
try {
return await operation ();
} catch (e) {
if (attempt == maxAttempts) rethrow ;
await Future . delayed ( Duration (seconds : attempt));
}
}
throw StateError ( 'Unreachable' );
}
// Debounce stream
Stream < T > debounce < T >( Stream < T > source, Duration duration) async * {
Timer ? timer;
T ? lastValue;
var hasValue = false ;
await for ( var value in source) {
timer ? . cancel ();
lastValue = value;
hasValue = true ;
timer = Timer (duration, () {
if (hasValue) {
hasValue = false ;
}
});
}
}