Dart Fundamentals: The Language Behind Flutter · Classes, Modern Dart 3 Features and Async

Async Dart: Future, async/await and Streams

Dart runs on an event loop: a Future is a value that arrives later, async/await makes it read like normal code, Future.wait runs work concurrently, and a Stream delivers many values over time.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A dashboard loader that fetches a user, their orders and notifications (simulated with delays), runs independent requests concurrently, applies a timeout, and listens to a stream of live price updates.

The idea

Dart code runs on a single thread with an event loop (like JavaScript). Slow work (network, files, timers) doesn't block; it returns a Future<T>: a value (or error) that arrives later.

  • Mark a function async and await futures inside it; the code reads top to bottom but doesn't block the app.
  • Future.wait([...]) (or awaiting a record of futures) runs independent operations concurrently and waits for all of them.
  • .timeout(duration) fails a future that takes too long.
  • A Stream<T> delivers many values over time (button taps, websocket messages, file chunks). Consume it with await for, or transform it with map, where, take.
  • async* functions produce streams with yield.
  • For CPU-heavy work that would freeze the UI, use isolates (Isolate.run), Dart's separate workers with their own memory.

The code

Concurrent futures, timeouts and an async* stream
import 'dart:async';

Future<String> fetchUser() => Future.delayed(const Duration(milliseconds: 120), () => 'Ana');
Future<int> fetchOrderCount() => Future.delayed(const Duration(milliseconds: 150), () => 3);
Future<List<String>> fetchNotifications() =>
    Future.delayed(const Duration(milliseconds: 100), () => ['Your order shipped']);
Future<String> fetchRecommendations() =>
    Future.delayed(const Duration(seconds: 2), () => 'too slow to matter');

Stream<double> livePrice(double start) async* {
  var price = start;
  for (final change in [0.5, -1.25, 2.0, -0.25]) {
    await Future.delayed(const Duration(milliseconds: 20));
    price += change;
    yield price; // each yield sends one value to listeners
  }
}

Future<void> main() async {
  final watch = Stopwatch()..start();

  final user = await fetchUser(); // sequential: wait for each in turn
  final orders = await fetchOrderCount();
  final sequential = watch.elapsedMilliseconds;

  watch.reset();
  final (user2, orders2, notes) = await (fetchUser(), fetchOrderCount(), fetchNotifications()).wait;
  final concurrent = watch.elapsedMilliseconds;

  print('$user has $orders orders; $user2 has $orders2 orders and ${notes.length} notification(s)');
  print('concurrent was faster: ${concurrent < sequential}');

  try {
    await fetchRecommendations().timeout(const Duration(milliseconds: 200));
  } on TimeoutException {
    print('recommendations timed out: showing the page without them');
  }

  await for (final price in livePrice(100)) {
    print('price: ${price.toStringAsFixed(2)}');
  }

  final bigMoves = await livePrice(100).where((p) => p > 101).toList();
  print('prices above 101: $bigMoves');
}

Output

Ana has 3 orders; Ana has 3 orders and 1 notification(s)
concurrent was faster: true
recommendations timed out: showing the page without them
price: 100.50
price: 99.25
price: 101.25
price: 101.00
prices above 101: [101.25]

Run this on your computer — see “Get Started” for setup.

The timing line compares instead of printing milliseconds, because exact times vary from run to run. The concurrent version takes about as long as the slowest request (150 ms) rather than the sum.

Common mistakes

  • Awaiting independent calls one by one, which adds their latencies together; start them together with Future.wait or a record's .wait.
  • Forgetting await, so errors go unhandled and code runs before the result arrives (the analyzer's unawaited_futures lint helps).
  • Never cancelling stream subscriptions in long-lived UIs; cancel them in dispose.
  • Heavy computation on the main isolate, which freezes Flutter animations; use Isolate.run.
  • No timeouts on network calls.

Key points

  • Future<T> = a value later; async/await keeps code readable.
  • Run independent futures concurrently (Future.wait, record .wait); add .timeout.
  • Stream<T> = many values over time; await for, async*/yield.
  • Isolates for CPU-heavy work.

Further reading: Asynchronous programming: futures, async, await, Streams, Isolates.

Practice questions

How do you run several independent async operations at the same time in Dart?

Answer

Start them all before awaiting, then await them together with Future.wait([...]) or by awaiting a record of futures with .wait, e.g. final (a, b) = await (f1(), f2()).wait;. The total time is about the slowest one, not the sum.

What's the difference between a Future and a Stream?

Answer

A Future delivers a single value (or error) once; a Stream delivers a sequence of values (and errors) over time, consumed with await for or listen.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?