Skip to content

📝 Fix StreamingResponse async generator example to actually stream#14993

Closed
Vishnuuuu24 wants to merge 1 commit intofastapi:masterfrom
Vishnuuuu24:fix/streaming-response-async-generator-docs
Closed

📝 Fix StreamingResponse async generator example to actually stream#14993
Vishnuuuu24 wants to merge 1 commit intofastapi:masterfrom
Vishnuuuu24:fix/streaming-response-async-generator-docs

Conversation

@Vishnuuuu24
Copy link

@Vishnuuuu24 Vishnuuuu24 commented Feb 24, 2026

Summary

Fixes #14680.

The StreamingResponse documentation example uses an async generator (fake_video_streamer) without any await expression. This causes two problems:

  1. No true streaming — without an await point, the async generator holds the event loop for the entire iteration, so the response is buffered entirely and sent all at once instead of being streamed chunk by chunk.
  2. No proper cancellation — in asyncio, a task can only be cancelled when it reaches an await. Without one, the generator cannot be interrupted and may keep running after cancellation is requested.

Changes

1. Code example (docs_src/custom_response/tutorial007_py310.py)

Added import asyncio and await asyncio.sleep(0) inside the async generator loop. This is the minimal fix to yield control back to the event loop between iterations.

2. Documentation (docs/en/docs/advanced/custom-response.md)

  • Updated highlighted line numbers to reflect the new asyncio import and await line.
  • Added a /// note admonition after the code example explaining why the await is necessary (event loop yielding, streaming, cancellation) and suggesting sync generators as an alternative.

Only the English docs are updated — translations are handled separately per project convention.

Validation

  • Existing test (tests/test_tutorial/test_custom_response/test_tutorial007.py) passes.
  • ruff check and ruff format pass cleanly.
  • Verified with a live uvicorn server that chunks now arrive incrementally (10 distinct chunks over ~377ms) instead of all at once.

Copilot AI review requested due to automatic review settings February 24, 2026 18:35
@github-actions github-actions bot added the docs Documentation about how to use FastAPI label Feb 24, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 24, 2026

Merging this PR will not alter performance

✅ 20 untouched benchmarks


Comparing Vishnuuuu24:fix/streaming-response-async-generator-docs (355f2cf) with master (daba0aa)

Open in CodSpeed

@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the FastAPI StreamingResponse documentation and its referenced example so the async generator yields control to the event loop, making the example behave like real incremental streaming.

Changes:

  • Added await asyncio.sleep(0) (and import asyncio) to the async generator in the StreamingResponse tutorial example.
  • Updated the docs snippet highlighting and added a tip explaining the need for an await when using async generators for streaming.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
docs_src/custom_response/tutorial007_py310.py Makes the documented async generator include an await so streaming can yield control between chunks.
docs/en/docs/advanced/custom-response.md Updates the StreamingResponse section snippet highlighting and adds a tip about async generator streaming behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -6,6 +8,7 @@

async def fake_video_streamer():
for i in range(10):
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop variable i is unused; consider renaming it to _ to avoid implying the value is used and to keep the example focused.

Suggested change
for i in range(10):
for _ in range(10):

Copilot uses AI. Check for mistakes.
Comment on lines 174 to 176
When using an **async generator**, make sure it contains at least one `await` expression (e.g. `await asyncio.sleep(0)`) to yield control back to the event loop between iterations. Without an `await`, the generator will run synchronously and the response will be buffered entirely before being sent to the client, instead of being streamed incrementally.

If your generator wraps synchronous/blocking work and has no natural `await` points, consider using a **normal generator** with a `def` function instead.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tip text is quite absolute (e.g. "Without an await, the generator will run synchronously and the response will be buffered entirely"). Even without an await inside the async generator, each yield still suspends execution; the real issue is typically blocking/CPU-bound work between yields or producing all chunks back-to-back so the transport coalesces them. Consider rephrasing to clarify that an await (or other pause/offloading) is needed to avoid blocking the event loop / to observe incremental delivery, rather than stating that async generators inherently buffer without await.

Suggested change
When using an **async generator**, make sure it contains at least one `await` expression (e.g. `await asyncio.sleep(0)`) to yield control back to the event loop between iterations. Without an `await`, the generator will run synchronously and the response will be buffered entirely before being sent to the client, instead of being streamed incrementally.
If your generator wraps synchronous/blocking work and has no natural `await` points, consider using a **normal generator** with a `def` function instead.
When using an **async generator**, make sure it includes at least one `await` (or other non-blocking pause/offloading) between iterations, for example `await asyncio.sleep(0)` or `await some_async_io()`. This yields control back to the event loop so it can send each chunk as it is produced, instead of running all the work in a tight loop where the client only sees the response once most of the work has completed.
If your async generator wraps mostly synchronous or CPU-bound work and has no natural `await` points, it may still behave as if the response is buffered because the event loop cannot interleave other tasks. In that case, consider using a **normal generator** with a `def` function and/or offloading the blocking work appropriately, while yielding smaller chunks.

Copilot uses AI. Check for mistakes.
@Vishnuuuu24 Vishnuuuu24 force-pushed the fix/streaming-response-async-generator-docs branch from 5b906af to 543fd2b Compare February 24, 2026 18:45
Add `await asyncio.sleep(0)` to the async generator in the
StreamingResponse documentation example so it properly yields
control back to the event loop between iterations, enabling
true incremental streaming and proper task cancellation.

Without an await point the async generator holds the event loop
for the entire iteration, buffering the full response before
sending it to the client.

Also add a note admonition explaining why the await is needed
and suggesting sync generators as an alternative when there are
no natural await points.

Closes fastapi#14680
@YuriiMotov
Copy link
Member

YuriiMotov commented Feb 25, 2026

This is a duplicate of #14681
In general it's better to review existing PR and suggest changes, rather than opening duplicate PRs (unless you have really divergent approaches or existing PR is not maintained).

Let's close this and I encourage you review existing PR

@Vishnuuuu24, thank you!

@YuriiMotov YuriiMotov closed this Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation about how to use FastAPI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StreamingResponse doc example needs correction

3 participants