Skip to main content
An asynchronous function in Dart is a subroutine marked with the async modifier that enables non-blocking execution within the single-threaded event loop. It allows the program to suspend execution at specific points to wait for Future completion while yielding control back to the event loop, preventing the blocking of the main isolate.

Syntax Structure

An asynchronous function is defined by placing the async keyword between the function signature and the function body.
// Standard async function returning a Future
Future<int> calculateValue() async {
  var result = await someLatentOperation();
  return result;
}

Return Type Semantics

The async modifier alters how values are returned to the caller.
  1. Future<T> Return: If the function signature declares a return type of Future<T>, the function wraps the returned value in a Future.
    • Implicit Wrapping: If the body returns a value of type T, Dart automatically wraps it in a Future<T>.
    • Immediate Return: When the function body encounters the first await or suspends, it immediately returns an uncompleted Future to the caller.
  2. void Return: If the function signature declares a return type of void, no object is returned to the caller. This is primarily used for event handlers where the caller cannot await the result or handle errors produced by the function.
// Returns a Future<int> that completes with 42
Future<int> getNumber() async {
  return 42; 
}

// Returns nothing; errors inside here cannot be caught by the caller
void fireAndForget() async {
  await logAnalytics();
}

The await Expression

The await keyword pauses the execution of the surrounding scope until the operand (a Future) completes.
  • Scope Validity: The await keyword is valid within the body of a function marked async and in the top-level scope of a library (top-level await).
  • Unwrapping: If the awaited Future completes with a value, the expression evaluates to that value.
  • Non-blocking Suspension: While execution is paused at an await, the Dart isolate is free to process other events in the event loop.
Future<String> processData() async {
  // Execution suspends. Control yields to the event loop.
  // When fetchRawData() completes, execution resumes.
  String data = await fetchRawData(); 
  return data.toUpperCase();
}

Execution Mechanics

The lifecycle of an asynchronous function follows a specific sequence:
  1. Synchronous Prefix: The function executes synchronously from the start of the body until it encounters the first await expression.
  2. Suspension and Return: Upon evaluating an await, the function registers a continuation and suspends. If the return type is Future<T>, an uncompleted Future is returned to the caller immediately.
  3. Resumption: Once the awaited operation completes, the runtime schedules the function’s continuation in the microtask queue.
  4. Completion: The function resumes execution.
    • Success: If the function body terminates normally (via return or reaching the end), the Future returned in step 2 completes with the result value.
    • Failure: If the function terminates due to an uncaught exception, the Future returned in step 2 completes with that error.

Error Handling

Asynchronous functions integrate with Dart’s exception handling model.
  • In-Function Handling: If an awaited Future completes with an error, the await expression throws that error as an exception, which can be caught using try-catch blocks within the async function.
  • Propagation: If an exception is not caught within the async function, the exception propagates out by completing the returned Future with an error state.
Future<void> reliableOperation() async {
  try {
    // If riskyOperation fails, the error is thrown here
    var value = await riskyOperation();
  } catch (e) {
    // Handle error synchronously
    print('Error caught: $e');
  }
}
Master Dart with Deep Grasping Methodology!Learn More