demo · v148

Streaming vs Sync

Both lanes are fetching the same HTML over a slow simulated connection. The left lane pipes the fetch body into the target via streamHTMLUnsafe(). The right lane awaits the full response then assigns it with setHTMLUnsafe(). Hit Run and watch them race.

About this demo: The "fetch" is a local ReadableStream we control directly — the slider on the right lets you set the inter-chunk delay so the difference is visible. In a real app, this is what your TTFB-to-fully-loaded curve looks like when you upgrade fetch().then(r => r.text()).then(t => (el.innerHTML = t)) to fetch().then(r => r.body.pipeThrough(new TextDecoderStream()).pipeTo(el.streamHTMLUnsafe())).

streamHTMLUnsafe()

first paint — ms · done — ms

setHTMLUnsafe() after full fetch

first paint — ms · done — ms

the code

// Streaming lane: pipe the fetch body straight into the element
const sink = target.streamHTMLUnsafe();
const res = await fetch(url);
await res.body
  .pipeThrough(new TextDecoderStream())
  .pipeTo(sink);

// Sync lane: collect the whole body first, then assign
const res = await fetch(url);
const text = await res.text();
target.setHTMLUnsafe(text);

The streaming version starts painting from the first chunk. The sync version paints once, at the end. The longer the body or the slower the connection, the bigger the gap.

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗