Async Programming

Dart async programming — Futures, Streams, Completers, and Isolates

Dart executes code on a single thread by default. Asynchronous programming with Future and Stream prevents blocking on I/O. For CPU-heavy work, use Isolate.

Future

A Future<T> represents a value of type T available at some point in the future.

// Creating
Future<String> fetchName() async {
  await Future.delayed(Duration(seconds: 1));
  return 'Ada';
}

// Immediately completed
Future.value(42);
Future.error(Exception('failed'));

// Delayed
Future.delayed(Duration(seconds: 2), () => 'done');

// Using async/await
void main() async {
  var name = await fetchName();
  print(name);  // Ada
}

Error handling

try {
  var result = await fetchData();
} on FormatException catch (e) {
  print('Format error: $e');
} catch (e) {
  print('Error: $e');
} finally {
  cleanup();
}

// then/catchError (alternative)
fetchData()
    .then((data) => process(data))
    .catchError((e) => handleError(e))
    .whenComplete(() => cleanup());

Combining futures

// Wait for all
var results = await Future.wait([
  fetchUser(),
  fetchPosts(),
  fetchComments(),
]);

// Wait for any (first to complete)
var fastest = await Future.any([
  server1.fetch(),
  server2.fetch(),
]);

// Timeout
var result = await fetchName().timeout(Duration(seconds: 5));

Stream

A Stream<T> is a sequence of asynchronous events.

// Creating with async*
Stream<int> countDown(int from) async* {
  for (var i = from; i >= 0; i--) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

// Creating with StreamController
import 'dart:async';

final controller = StreamController<String>();

controller.add('hello');
controller.add('world');
controller.close();

controller.stream.listen(
  (event) => print(event),
  onError: (error) => print('Error: $error'),
  onDone: () => print('Done'),
);

Stream transformations

stream
    .map((e) => e.toUpperCase())
    .where((e) => e.startsWith('H'))
    .distinct()
    .take(5)
    .skip(2)
    .expand((e) => [e, e])
    .asyncMap((e) async {
      await Future.delayed(Duration(milliseconds: 100));
      return e;
    })
    .asyncExpand((e) => Stream.fromIterable([e, e]));

// Convert to list
var all = await stream.toList();

// First and last
var first = await stream.first;
var last = await stream.last;

// Fold
var total = await stream.fold<int>(0, (acc, e) => acc + e);

Broadcast streams

// Single-subscription (default) — one listener at a time
var stream = Stream.fromIterable([1, 2, 3]);

// Broadcast — multiple listeners
var broadcast = stream.asBroadcastStream();
broadcast.listen((e) => print('A: $e'));
broadcast.listen((e) => print('B: $e'));

// Broadcast controller
var controller = StreamController<String>.broadcast();

Completer

Manually complete a Future:

import 'dart:async';

Future<T> withTimeout<T>(Future<T> future, Duration timeout) {
  var completer = Completer<T>();
  future.then(completer.complete).catchError(completer.completeError);
  Future.delayed(timeout, () {
    if (!completer.isCompleted) {
      completer.completeError(TimeoutException('Timeout'));
    }
  });
  return completer.future;
}

Isolates

For CPU-intensive work, use isolates to run code in parallel:

import 'dart:isolate';

// Simple isolate (Dart 2.19+)
Future<int> heavyComputation(int count) async {
  return await Isolate.run(() {
    var sum = 0;
    for (var i = 0; i < count; i++) {
      sum += i;
    }
    return sum;
  });
}

// Using
void main() async {
  var result = await heavyComputation(1000000);
  print(result);
}

Isolate with message passing

import 'dart:isolate';

void worker(SendPort sendPort) {
  var result = fibonacci(40);
  sendPort.send(result);
}

int fibonacci(int n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

Future<void> main() async {
  var receivePort = ReceivePort();
  await Isolate.spawn(worker, receivePort.sendPort);
  var result = await receivePort.first;
  print(result);
}

Compute in Flutter

import 'package:flutter/foundation.dart';

// Flutter's compute function
var result = await compute(heavyWork, data);

int heavyWork(int input) {
  // runs in a separate isolate
  return input * 2;
}