# yield — a function that produces results over time

> A `stream<T>` function produces its results one at a time instead of all at once, and a `live var` bound to one renders each item the moment it arrives. Use it for anything whose answer builds up rather than appearing — an assistant's reply, a log tail, a long import's progress, a search that finds matches as it goes.

<!-- id: function-yield · area: function · stability: stable · html: https://osysharp.com/reference/function/yield/ -->

## Summary        {#summary}
Most functions answer once: you call them, they think, they return. Some answers do not work that way — an
assistant's reply arrives a word at a time, a log tail never finishes at all, an import of fifty thousand rows has
something useful to say long before it is done.

A **`stream<T>`** function produces its results one at a time:

```osy title="the producer side — a function that answers many times" test app=function-yield-tail
stream<string> Tail(string path) {
  foreach (var line in Lines(path)) {
    yield return line;
  }
}

string[] Lines(string path) { return [path]; }
```

A component binds it with an ordinary `live var`, and renders each item as it arrives:

```osy title="the consumer side — a live var renders items as they land" test app=function-yield-tail
component LogView(string Path) {
  live var lines = Tail(Path);

  render {
    Stack(overflowY: Overflow.Auto, stickToBottom: true) {
      foreach (var line in lines) { Text(line); }
    }
  }
}
```

Nothing polls, nothing re-fetches, and no item is rendered twice.

## Signature      {#signature}
```osy syntax
stream<T> Name(args) { … yield return item; … }   // declare a producer

live var items = Name(args);                      // observe it; items appear as they arrive
foreach (var item in items) { … }                 // renders each one, once
items.Count                                       // how many so far
items.Failed · items.Error                        // it stopped early, and why
items.Interrupted                                 // …and it was the CONNECTION that dropped, so a retry may work
items.Done                                        // the producer finished
```

## Description    {#description}

### What `yield return` does    {#yield}
`yield return x;` hands one item to the caller **and keeps going**. The function does not end — the next statement
runs, and the next `yield return` delivers the next item. When the function reaches its end, the stream is complete.

```osy title="yield return hands one item over and carries on" test app=function-yield-search
entity Document { [MaxLength(200)] string Title; [MaxLength(4000)] string Body; }

class Match { public string Title; public string Snippet; }

stream<Match> Search(string term) {
  foreach (var doc in Document.Where(d => d.Body.Contains(term))) {
    yield return new Match { Title = doc.Title, Snippet = doc.Body };
  }
}
```

To stop early, `return;` on its own — the stream completes normally, with whatever it produced so far.

```osy title="a bare return stops early and completes the stream" test app=function-yield-firstpage
stream<string> FirstPage(string path) {
  var n = 0;
  foreach (var line in Lines(path)) {
    if (n >= 100) { return; }          // enough — complete the stream
    n = n + 1;
    yield return line;
  }
}

string[] Lines(string path) { return [path]; }
```

> **Coming from C#?** There is no `yield break` here. C# needs it because a bare `return;` in an iterator is
> ambiguous with returning a value; a `stream<T>` function never returns a value, so `return;` is unambiguous and
> means exactly what `yield break` means in C#. Everything else is the same, including `yield return` itself.

### A stream is observed, never awaited    {#not-awaitable}
A `stream<T>` can only be bound to a `live var`. Calling one anywhere else is a compile error, because there is no
"the whole thing" to hold — the answer is still arriving:

```osy syntax
var lines = Tail(path);        // ✗ a stream has no single value to assign
on mount { Tail(path); }       // ✗ same reason
live var lines = Tail(path);   // ✓
```

This is the same distinction [The reactivity & lifecycle model](https://osysharp.com/reference/ui/reactivity/) already draws. A `live var` is a **value binding** — it says what a
value *is*, continuously — and a stream is exactly that: a collection that is still being written.

### Why an ordinary server function cannot be a `live var`    {#why-not-ordinary}
[The reactivity & lifecycle model](https://osysharp.com/reference/ui/reactivity/) refuses `live var files = FilesInFolder(id);` because nothing subscribes that value to anything —
it would be fetched once and then quietly go stale, or force a hand-off to the server mid-render.

A stream removes that objection rather than working around it: **the stream itself is the subscription.** The server
holds the connection open and pushes; there is nothing to poll and nothing to invalidate. That is why `stream<T>` is
allowed exactly where an ordinary server call is not.

### Items only ever arrive — they are never revised    {#append-only}
A stream is **append-only**. There is no way to change or remove an item once it has been yielded, and that is a
guarantee rather than a missing feature: it is what producers actually do (an assistant never un-says a word, a log
never un-writes a line), and it is what keeps rendering cheap. Appending touches the end of the list, so the items
already on screen are left alone — see [Markdown — rendering markdown text](https://osysharp.com/reference/ui/markdown/), whose renderer keeps the DOM of every block that did not
change.

If you need to *replace* a value as it evolves, that is an ordinary reactive read, not a stream.

### How does a stream end, and how do I tell which way?    {#completion}
Four ways, and a component can tell them apart:

| | `Done` | `Failed` | `Interrupted` | what to show |
|---|---|---|---|---|
| the function ended | `true` | `false` | `false` | the finished list |
| the function raised an error | `false` | `true` | `false` | the items so far, plus `Error` |
| the connection dropped | `false` | `true` | `true` | the items so far, and an offer to retry |
| still producing | `false` | `false` | `false` | the items so far, and usually a spinner |

**`Interrupted` narrows `Failed`; it does not replace it.** A dropped connection is both, so a component that only
checks `Failed` still shows something — where two mutually exclusive flags would leave it waiting forever on a drop.

**A dropped connection does not stop the producer, and the platform reconnects for you.** The producer's life is its
own: it keeps running on the server while the browser is away, and reconnecting *resumes reading the same run* from
the item you already have. Nothing is re-run, so item 40 is the same item 40 — which is what makes reconnecting
automatic rather than a way to splice the first half of one answer onto the second half of another. A few attempts
are made, backing off; `Interrupted` is what you hear when they are spent, so it means "this is not coming back",
not "the connection blinked".

Offering a retry is therefore about starting *again*, and that is meaningful only when re-running the producer would
produce the same items. A tail of a file or a read of stored rows will; an assistant's reply will not, because a
second run writes a different answer — so an app that streams replies is usually better asking the question again
than resuming it.

**Leaving stops it.** A component that unmounts, or a page that navigates away, tells the server it is going — the
producer stops immediately, rather than running on for the grace window that covers a genuine drop.

**An answer that finished while you were away is kept.** If the connection dropped and the producer went on to
finish with nobody reading, the platform keeps that answer so the reader can still collect it — across a restart, not
only for the few minutes it stays in memory. How long, and how much, are the platform's to decide: there is no
attribute to write and no knob to get wrong.

**A stream that fails keeps everything it already produced.** A reply that broke off halfway still said what it
said, and the reader has already read it — discarding it would destroy the only record of how far it got.

```osy test app=function-yield-answer
stream<string> Ask(string question) { yield return question; }

component Answer(string Question) {
  live var reply = Ask(Question);

  render {
    Stack {
      foreach (var part in reply) { Markdown(part, streaming: !reply.Done); }
      if (reply.Failed) { Text(reply.Error); }
      else if (!reply.Done) { Text("…"); }
    }
  }
}
```

### Where it runs    {#where-it-runs}
`stream<T>` is a **server** producer — that is decided by the declaration itself, not by anything you write — and
its items cross to the browser as they are produced, over the caller's own connection. Items go to the component that asked for them and to nothing else — a stream is never broadcast, and one
visitor's results are never visible to another.

Everything a stream reads obeys the same rules any server read does. There is nothing extra to declare and nothing
extra to check.

### Streaming markdown    {#markdown}
The common case for a text stream is markdown that is still being written, which [Markdown — rendering markdown text](https://osysharp.com/reference/ui/markdown/) handles directly:

```osy syntax
live var reply = Ask(question);

render {
  Stack(overflowY: Overflow.Auto, stickToBottom: following) {
    foreach (var part in reply) {
      Markdown(part, streaming: !reply.Done);
    }
  }
}
```

`streaming:` holds a half-typed construct together so the reader never sees raw markdown syntax, and
`stickToBottom:` follows the new text without yanking a reader who has scrolled up ([layout primitives](https://osysharp.com/reference/ui/layout/)).

## See also {#see-also}
- [The reactivity & lifecycle model](https://osysharp.com/reference/ui/reactivity/) — `live var`, and what may initialize one
- [Markdown — rendering markdown text](https://osysharp.com/reference/ui/markdown/) — rendering a stream of markdown as it arrives
- [layout primitives](https://osysharp.com/reference/ui/layout/) — `stickToBottom`, for a surface that grows while you read it
