dart:async library

The dart:async library delivers core capabilities for asynchronous programming in Dart: Future, Stream.

  1. Using Future / Stream does not always require manually importing dart:async; these APIs are already exported via dart:core.
  2. You must import the library explicitly to work with types such as StreamController, StreamSubscription, ParallelWaitError:
import 'dart:async';Code language: JavaScript (javascript)

Two foundational concepts:

  • Future: Represents a single-shot asynchronous result (emits exactly one value or one error at some point in the future)
  • Stream: Represents a continuous sequence of asynchronous data (produces multiple values over time, similar to an event stream)

Future

1. async / await

Recommended by convention; offers better readability compared to .then() chaining

  • Any function marked async returns a Future;
  • await pauses execution until the Future completes and yields its result;
  • Use try-catch blocks to handle asynchronous errors.
// async functions return Future
Future<String> findEntryPoint() async => "main.dart";
Future<int> runExecutable(String entry, List args) async => 0;
Future<void> flushThenExit(int code) async {}

// await syntax (preferred)
Future<void> runUsingAsyncAwait() async {
  try {
    var entryPoint = await findEntryPoint();
    var exitCode = await runExecutable(entryPoint, []);
    await flushThenExit(exitCode);
  } catch (e) {
    // Catch all errors uniformly
    print("Error: $e");
  }
}Code language: JavaScript (javascript)

2. Traditional chaining: .then() / .catchError()

  • .then(): Runs a callback once the Future resolves successfully. It returns a new Future to support continuous chaining
  • .catchError(): Catches thrown errors; must be chained after the return value of then(), otherwise errors inside then() cannot be captured
void runUsingFuture() {
  findEntryPoint()
      .then((entryPoint) {
        return runExecutable(entryPoint, []);
      })
      .then(flushThenExit)
      .catchError((e) {
        print("Exception: $e");
      });
}Code language: JavaScript (javascript)

3. Awaiting multiple concurrent Futures

① Future.wait()

Waits for every task to finish; the whole operation fails immediately if any single Future throws an error

Future<void> task1() async {}
Future<void> task2() async {}
Future<void> task3() async {}

void testWait() async {
  await Future.wait([
    task1(),
    task2(),
    task3(),
  ]);
  print("All tasks completed");
}Code language: JavaScript (javascript)
② List.wait / Record.wait (new feature, fine-grained concurrent error handling)

Throws a ParallelWaitError, letting you identify which operations succeeded and which failed without mutual interruption.

Future<int> delete() async => 1;
Future<String> copy() async => "ok";
Future<bool> errorTask() async {
  throw Exception("Task failed");
}

void main() async {
  try {
    // List variant
    var results = await [delete(), copy(), errorTask()].wait;
  } on ParallelWaitError<List<Object?>, List<AsyncError?>> catch (e) {
    print(e.values);   // Contains values for successful tasks, null for failures
    print(e.errors);   // Null for successes, stores error details for failures
  }

  // Record tuple variant: supports Futures of different types
  try {
    final (resInt, resStr, resBool) = await (delete(), copy(), errorTask()).wait;
  } on ParallelWaitError<(int?, String?, bool?), (AsyncError?, AsyncError?, AsyncError?)> catch(e) {

  }
}Code language: JavaScript (javascript)

Stream

A Stream represents a continuous sequence of asynchronous data (examples: line-by-line file reading, button click events, network data streams).

Two common usage patterns

1: await for asynchronous for-loop (concise, similar to synchronous iteration)

Suitable for finite data streams; avoid for unbounded ongoing event streams (such as UI click events) as it blocks subsequent code execution.

import 'dart:io';

Future<void> readFileByAwaitFor() async {
  var file = File("config.txt");
  Stream<List<int>> input = file.openRead();
  var lines = input
      .transform(utf8.decoder)
      .transform(const LineSplitter());

  try {
    await for (final line in lines) {
      print("Read line: $line");
    }
    print("File reading finished");
  } catch (e) {
    print("Read error $e");
  }
}Code language: PHP (php)
2: .listen() subscription listening (for unbounded event streams)
inputStream.listen(
  (data) {
    // Callback triggered on incoming data
  },
  onError: (err) {
    // Triggered when an error occurs
  },
  onDone: () {
    // Runs when the stream closes normally
  },
);Code language: JavaScript (javascript)

listen() returns a StreamSubscription, which you can terminate by calling .cancel().

Frequently used Stream operators

  • Filter: where()
  • Take elements: take(), takeWhile()
  • Skip elements: skip(), skipWhile()
  • Fetch single element: first, last, single, firstWhere()

Stream data transformation with transform()

Use transformers to alter the data type of a stream (typical use case: convert binary streams to text strings)

var lines = inputStream
    .transform(utf8.decoder)       // List<int> → String
    .transform(const LineSplitter()); // String → individual text linesCode language: JavaScript (javascript)

Choosing between listen() and await for

  1. File I/O, finite data sequences → await for
  2. UI events, continuous push messages and other unbounded streams.listen()
  3. Once started, await for waits indefinitely until stream termination; unbounded event streams cause code to hang

dart:async library

Previous:

Leave a Reply

Your email address will not be published. Required fields are marked *