Async Views & ASGI

An async view is a Flask route defined with async def so you can await I/O inside it, letting several slow waits overlap instead of running one after another.

Learn Async Views & ASGI in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll write async views, run concurrent I/O with asyncio.gather , await an async client, and learn the WSGI limits and the ASGI servers that lift them.

Since Flask 2.0 a view can be async def . Inside it you can await coroutines, and Flask runs the view on an event loop for you — the rest of your code (routing, the test client, returning JSON) is unchanged. The only setup is installing the async extra, which pulls in asgiref .

The runnable example defines an async view that await s a coroutine and returns JSON. The test client calls it exactly like any other route.

The async view returns 200 with its JSON just like a sync view — from the caller's side nothing looks different. The power comes from what you can await inside.

The payoff is overlapping waits. If a view needs three slow API calls, doing them sequentially takes the sum of their times. With asyncio.gather they run concurrently , so the total is roughly the slowest single call — not all of them added up.

The runnable example fires three awaitable "fetches" and times them. Because gather overlaps the waits, the elapsed time is close to one delay, not three — the code proves it with a concurrent flag.

Three 50 ms waits finish in about 50 ms, not 150 ms, because they overlapped. That is the entire reason to write an async view.

In real code you await an async-aware library like httpx.AsyncClient . The runnable example uses a small stub that mimics its async with / await client.get(...) shape so the demo runs offline, but the structure is exactly what you'd write with httpx.

Complete the route below. Replace each ___ so it's an async view that awaits and returns JSON.

Async views need asgiref. Run pip install "flask[async]" so Flask can run async def views on an event loop.

❌ The async view is no faster than a sync one

You awaited a blocking call (like requests.get or a sync DB query). Blocking calls freeze the event loop. Use async libraries ( httpx.AsyncClient ) or run blocking work in a thread executor.

Fetch several prices concurrently and combine them in an async view.

Lesson complete — you can overlap slow I/O in a view!

You wrote async def views, overlapped waits with asyncio.gather , awaited an async client, and learned WSGI's one-request limit and the ASGI deployment that unlocks real concurrency.

🚀 Up next: Checkpoint — Production-Ready Flask — combine everything you've learned into one hardened endpoint.

Practice quiz

How do you define an async view in Flask?

  • with @app.async_route
  • with async def
  • with @asyncio.view
  • with await def

Answer: with async def. An async view is a route function defined with async def.

Since which version has Flask supported async views?

  • Flask 0.12
  • Flask 1.0
  • Flask 2.0+
  • Flask never supports them

Answer: Flask 2.0+. Flask 2.0+ supports async def views, running them on an event loop.

What must you install for async views?

Install the async extra with pip install 'flask[async]', which adds asgiref.

When does async actually help?

  • CPU-bound number crunching
  • A single quick query
  • I/O-bound work with overlapping waits
  • Rendering templates

Answer: I/O-bound work with overlapping waits. Async shines for I/O-bound work where awaits can overlap.

Which call runs several awaitable I/O operations concurrently?

  • asyncio.gather(...)
  • asyncio.run_all(...)
  • await sequentially
  • thread.join()

Answer: asyncio.gather(...). asyncio.gather runs the awaitables concurrently.

Does an async view make plain WSGI Flask non-blocking across requests?

  • Yes, fully non-blocking
  • No, the worker still handles one request at a time
  • Only with threads
  • Only in debug mode

Answer: No, the worker still handles one request at a time. Under WSGI the worker still serves one request at a time; awaits overlap only within a request.

What is ASGI?

  • A database driver
  • A template engine
  • A caching backend
  • The async successor to WSGI

Answer: The async successor to WSGI. ASGI is the asynchronous successor to WSGI.

How do you wrap a Flask app to serve it under ASGI?

  • WsgiToAsgi(app) from asgiref
  • Flask(async=True)
  • app.to_asgi()
  • asyncio.wrap(app)

Answer: WsgiToAsgi(app) from asgiref. asgiref's WsgiToAsgi(app) wraps the WSGI app for an ASGI server.

Which client works correctly with await in an async view?

  • requests.get
  • urllib.request
  • httpx.AsyncClient
  • a synchronous DB query

Answer: httpx.AsyncClient. await works with async-aware libraries like httpx.AsyncClient.

What happens if you await a blocking call like requests.get in an async view?

  • It becomes async automatically
  • It blocks the event loop
  • Flask raises an error
  • It runs in a separate process

Answer: It blocks the event loop. A blocking call still blocks the event loop even inside an async view.