Skip to main content
An asynchronous method in Dart is a function that executes non-blocking operations by yielding control back to the Dart event loop while awaiting a deferred computation. It is marked with the async or async* modifier and inherently wraps its return value in a Future (for single values) or a Stream (for a sequence of values).

Core Mechanics

The asynchronous model in Dart relies on the async, await, and yield keywords. When a function is marked with async, the Dart compiler transforms it into a state machine. Execution proceeds synchronously until the first await expression is encountered. At that point, the function suspends execution, saves its current state, and immediately returns an uncompleted Future to its caller. The underlying thread is not blocked; the event loop continues processing other events. Once the awaited operation completes, the function resumes execution from the exact point of suspension.

Syntax and Return Types

1. Single-Value Asynchronous Methods (async / Future)

A standard asynchronous method uses the async modifier. If it returns a value of type T, the return type must be Future<T>. If the function does not return a value, the return type is typically Future<void>. However, Dart explicitly allows async functions to return void for “fire-and-forget” operations, such as event handlers, where the caller does not need to await completion or catch exceptions.
// Mock dependencies
Future<String> fetchRawData() async => Future.value("raw_data");
String parse(String data) => data.toUpperCase();

// Returns a Future<String>
Future<String> processData() async {
  // Synchronous execution up to this point
  final rawData = await fetchRawData(); 
  
  // Execution resumes here after fetchRawData completes
  final parsedData = parse(rawData);
  
  // The returned value is automatically wrapped in a Future<String>
  return parsedData; 
}

// Returns void (fire-and-forget)
void logEvent() async {
  await Future.delayed(Duration(milliseconds: 10));
  print("Event logged.");
}

void main() async {
  final result = await processData();
  print("Processed: $result");
  
  logEvent();
}

/* Output:
Processed: RAW_DATA
Event logged.
*/

2. Asynchronous Generators (async* / Stream)

To return a sequence of asynchronous values over time, Dart uses asynchronous generator functions marked with async*. These methods return a Stream<T> and use the yield keyword to emit values to the stream. To delegate stream generation to another stream within an async* method, the yield* (yield-each) expression is used. To consume the values emitted by a Stream, Dart provides the await for loop. This loop suspends execution of the surrounding async function until the stream emits a new value, executes the loop body, and terminates when the stream is closed.
Stream<int> generateSequence(int max) async* {
  for (int i = 1; i <= max; i++) {
    // Suspends execution until the Future completes
    await Future.delayed(Duration(milliseconds: 10));
    
    // Emits the value to the Stream and continues the loop
    yield i; 
  }
}

Stream<int> delegateSequence() async* {
  // Delegates to another stream
  yield* generateSequence(3);
}

void main() async {
  // Consuming the stream using await for
  await for (final value in delegateSequence()) {
    print("Received: $value");
  }
}

/* Output:
Received: 1
Received: 2
Received: 3
*/

Error Handling

Asynchronous methods handle exceptions using standard try-catch-finally blocks. If an awaited Future completes with an error, the exception is thrown at the await expression, allowing it to be caught synchronously within the method’s scope. If an exception is not caught within the async method, the Future returned by the method will complete with that error, propagating it up the call stack to the caller.
// Mock dependency
Future<String> riskyOperation() async {
  await Future.delayed(Duration(milliseconds: 10));
  throw FormatException("Invalid data format");
}

Future<void> executeWithHandling() async {
  try {
    final result = await riskyOperation();
    print(result);
  } on FormatException catch (e) {
    // Handles specific exceptions thrown by the awaited Future
    print("Handled FormatException: ${e.message}");
  } catch (e) {
    // Handles all other exceptions
    print("Handled generic error: $e");
  } finally {
    // Executes regardless of success or failure
    print("Cleanup routine finished.");
  }
}

void main() async {
  await executeWithHandling();
}

/* Output:
Handled FormatException: Invalid data format
Cleanup routine finished.
*/
Tired of Poor Dart Skills? Fix That With Deep Grasping!Learn More