skip to content

In Dart, how does an `await for` loop consume a stream, and what happens on an error, a `break`, or an endless stream?

level: juniorimportance: should knowfreq 55%

answer

  1. only inside an async function
  2. body runs per event, then waits
  3. error events are rethrown
  4. break or return unsubscribes
  5. endless streams never finish the loop

basics

~20 s

await for, allowed only in async functions, runs its body per stream event until the stream closes. An error event is rethrown at the loop, break or return unsubscribes, and an endless stream keeps the function waiting forever.

solid answer

~40 s

`await for (final t in temps) { ... }` listens to the stream, runs the body for each data event, and then suspends the enclosing `async` function until the next event; when the stream sends done, the loop ends and the function continues. An **error event** is rethrown at the loop, so a surrounding `try`/`catch` handles it and the loop exits. `break` or `return` inside the body **unsubscribes** from the stream. Because the loop only ends when the stream closes, it is a poor fit for endless streams such as UI events: code after the loop never runs. It is only legal in an `async` (or `async*`) function, and each loop is one listen, so it throws on a single-subscription stream that already has a listener.

go deeper

for a junior

Know that await for runs its body once per event, needs an async function, and ends when the stream closes.

for a middle

Explain that errors are rethrown at the loop, that break or return unsubscribes, and why endless streams keep the function waiting forever.

for a senior

Choose between await for and listen by stream lifetime, and use loop exit to cancel generator sources cleanly.

for a principal

Encourage await for for finite, ordered processing and an explicit subscription owner for long-lived streams, so lifetimes are visible in the code.

## The loop in one picture `await for` is the stream counterpart of `await`. Its form is: ```dart await for (final reading in temps) { // runs once per data event } ``` The dart.dev language tour describes the execution precisely: 1. Wait until the stream emits a value. 2. Execute the body with the loop variable set to that value. 3. Repeat until the stream is closed. Behind the scenes the loop calls `listen` on the stream, so the usual subscription rules apply: a single-subscription stream that is already being listened to throws a `StateError` when the loop starts. ## Where it is allowed `await for` is legal only inside a function body marked `async` or `async*`. The language tour's advice for a compile error here is simply to check that the enclosing function, including `main`, is marked `async`. While the loop waits for the next event, the function is **suspended**, not blocking the isolate; other code keeps running. ## Errors If the stream emits an **error event**, `await for` **rethrows** it at the loop. That makes ordinary error handling work: ```dart Future<double> averageOf(Stream<double> temps) async { var sum = 0.0, n = 0; try { await for (final t in temps) { sum += t; n++; } } on SensorException { // the loop has exited; use what we have } return n == 0 ? double.nan : sum / n; } ``` Without the `try`, the error propagates out of the function: in an `async` function it fails the returned future, and in an `async*` function it is emitted on the output stream, which then closes. ## Stopping early - `break` or `return` inside the body leaves the loop and **unsubscribes** from the stream, per the language tour. For an `async*` source, that cancellation makes its next `yield` act as a `return`, so its `finally` blocks run. - This is the idiomatic way to take "the first reading above 30 degrees" and stop the sensor stream. ## `Stream.forEach` as the callback twin The SDK describes `Stream.forEach` as the method that corresponds to the `await for` loop, just as `Iterable.forEach` corresponds to a normal `for`-`in` loop: it calls a function for each data event, stops on an error, and returns a `Future` that completes when the stream is done. It is handy when you want loop-like consumption without writing an `async` function around it, for example `await temps.take(5).forEach(print);`. Like the loop, it listens to the stream, so it counts as the one listener of a single-subscription stream. ## When not to use it The language tour warns: **usually do not use `await for` for UI event listeners, because UI frameworks send endless streams of events.** A loop over a stream that never closes never finishes, so: - statements after the loop never run; - the function's future never completes; - the subscription is held for as long as the stream lives. For endless streams, `listen` with a stored `StreamSubscription` that you cancel later is the better tool. ## `await for` versus `listen` | | `await for` | `listen` | |---|---|---| | Style | sequential, inside `async` code | callbacks, anywhere | | Error handling | `try`/`catch` around the loop | `onError` handler | | Stopping | `break` or `return` | `subscription.cancel()` | | Next event while body awaits | handled only after the current iteration finishes | handler runs again even if earlier async work is still pending | | Suited to | finite streams processed in order | long-lived or endless streams | The fourth row is worth stating in an interview: inside `await for`, events are handled strictly one at a time, and the body, including its own awaits, completes before the next event is processed. With `listen`, an `onData` handler that starts async work returns immediately, so the next event can arrive while that work is still running. ## Summary `await for` gives streams the same readable, top-to-bottom style that `await` gives futures, as long as the stream ends. Use it for finite sequences, file lines and generator output; use `listen` for streams that run for the lifetime of a screen or service.

  • What happens to an `async*` source when the `await for` consuming it hits `break`?
    The loop unsubscribes, which cancels the subscription to the generator's stream. The next time the `async*` body reaches a `yield`, that `yield` acts as a `return`: enclosing `finally` blocks run and the function exits, so the source stops producing.
  • Can you use `await for` inside a synchronous event handler?
    No. `await for` is only allowed in an `async` or `async*` function body. A synchronous handler would need to call an `async` helper that contains the loop, or use `listen` directly, and in either case something must own the resulting future or subscription.

saying these in an interview costs you the question

  • await for blocks the isolate while it waits for events
  • An error event is skipped and the loop continues
  • break leaves the loop but the subscription stays active
  • await for is a good fit for endless UI event streams
  • await for can be used in any function body