In Dart, how does an `async*` function produce a stream with `yield` and `yield*`, and what happens to its body on pause and cancel?
answer
- returns a Stream at once
- body starts on first listen
- yield emits one, yield* forwards all
- pauses at yield while paused
- after cancel, yield acts as return
basics
~20 sAn async* function returns a single-subscription Stream immediately and runs its body only once listened to; yield emits one event and yield* forwards another stream's events. It pauses at yield while paused, and after cancel the next yield returns, running finally blocks.
solid answer
~40 sCalling an `async*` function returns a `Stream<T>` straight away, but **nothing runs until someone listens**; then the body starts. `yield value` emits one data event, `yield* otherStream` forwards all of another stream's data and error events until it closes, and when the body ends the stream closes. A thrown error is emitted on the stream, which then closes. The generator cooperates with its subscriber: while the subscription is **paused**, the body stops at its next `yield`; after the subscriber **cancels**, the next `yield` acts as a `return`, so enclosing `finally` blocks run, and the future returned by `cancel()` completes when the body has exited. Each call creates a new single-subscription stream, so a sensor generator called twice polls twice.
go deeper
Know that an async* function returns a Stream and uses yield to emit each value.
Explain that the body starts on listen, how yield* forwards another stream, and how errors and normal returns end the stream.
Rely on the generator's pause-at-yield and cancel-as-return behaviour, and put resource cleanup in finally so a cancelled stream releases what it opened.
Prefer async* producers where their automatic pause and cancel handling fits, and reserve hand-built producers for cases that need broadcast or external pushes.
## An asynchronous generator Marking a function body `async*` makes it an **asynchronous generator**: it returns a `Stream<T>` and produces that stream's events from ordinary-looking code. ```dart Stream<double> readings(Duration every) async* { final sensor = await Sensor.open(); try { while (true) { await Future.delayed(every); yield sensor.read(); } } finally { await sensor.close(); } } ``` Three keywords do the work: - **`yield value`** emits one data event. - **`yield* stream`** listens to another stream and forwards **all its events, data and errors**, until it closes; then execution continues after the `yield*`. - **`await`** works as in any `async` function, suspending until a future completes. ## Lazy start, unlike `async` An `async` function starts running synchronously as soon as it is called. An `async*` function does not: dart.dev states that **the stream is created when the function is called, and the body starts running when the stream is listened to**. So `final s = readings(oneSecond);` opens no sensor at all; the first `listen` or `await for` does. Each call returns a **new single-subscription stream** with its own run of the body. Two listeners on one returned stream are an error; two calls give two independent generators. ## How the stream ends 1. The body **returns** normally: the stream sends done. 2. The body **throws**, or an error arrives through `await for` inside it and is not caught: the error is **emitted** on the stream, which then **closes**. 3. The subscriber **cancels**: see below. ## Pause and cancel An `async*` function handles its subscriber's requests automatically, which is its main advantage over hand-written producers: | Subscriber does | Generator does | |---|---| | `pause()` | stops at the next `yield` until resumed, producing nothing meanwhile | | `resume()` | continues from that `yield` | | `cancel()` | the next `yield` acts as `return`: `finally` blocks run and the body exits | Details worth knowing: - Because the body stops at `yield` while paused, a paused generator does not build an ever-growing buffer. - After cancellation, a `yield` that would emit a value fails and acts as a return instead. - The `Future` returned by `subscription.cancel()` completes when the function has actually exited; if the body exits with an error, that future completes with the error. In the example, awaiting `cancel()` guarantees `sensor.close()` has finished. - Cleanup therefore belongs in `finally`, not after the loop, because a cancelled generator never reaches code after `while (true)`. ## `yield*` for composition `yield*` lets one generator delegate to another stream: ```dart Stream<double> calibrated(Stream<double> raw, double offset) async* { yield* raw.map((t) => t + offset); } Stream<double> session() async* { yield* readings(const Duration(seconds: 1)).take(3); yield double.nan; // marker after the first three readings } ``` Errors from the delegated stream are forwarded too, so a failure in `readings` reaches whoever listens to `session`. ## `async*` versus a hand-built producer dart.dev's guide to creating streams compares the two ways of producing events. An `async*` function **automatically pauses at a `yield`** while its subscription is paused, and stops at the next `yield` after a cancel. A producer built by hand with a controller, by contrast, **buffers** events during a pause unless it is written to honour the pause state, so a producer that ignores pauses can grow its buffer without limit. The generator is therefore the default for streams whose events come from the generator's own code, such as polling a sensor or reading lines. A hand-built producer is still needed when events are pushed in from outside, for example from callbacks, or when the stream must be broadcast from the start. ## Common mistakes - Expecting side effects before the first `listen`; the body has not started. - Putting cleanup after an infinite loop instead of in `finally`. - Listening twice to one returned stream instead of calling the function twice or sharing with `asBroadcastStream`. - Using `yield` with a stream value where `yield*` was intended, which emits the stream object as an event and does not compile when the element type is not a stream.
- What is the practical difference between `yield stream` and `yield* stream` in an `async*` function?`yield` emits exactly one event, so `yield stream` would emit the stream object itself and only compiles if the element type allows a stream. `yield*` subscribes to the stream and forwards each of its data and error events, continuing after it closes.
- Why does cleanup in an `async*` generator belong in a `finally` block?Because a cancelled generator exits at its next `yield`, which acts as a `return`. Code after an infinite loop is never reached, while enclosing `finally` blocks do run, and the future returned by `cancel()` completes only after they finish.
saying these in an interview costs you the question
- An async* body starts running as soon as the function is called
- yield* emits the inner stream as a single event
- A paused async* generator keeps producing and buffering values
- Cancelling the subscription kills the generator without running finally
- Two listeners can share one stream returned by an async* call