Topic 15 of 64
asyncio
Overview
Python's asyncio enables concurrent I/O operations without threads, using async/await syntax. It's the foundation of high-performance web frameworks (FastAPI, aiohttp) and is essential for building scalable APIs and web scrapers.
Syntax
python
import asyncio
import aiohttp
# async function — returns a coroutine
async def fetch_user(session, user_id: int) -> dict:
async with session.get(f"/api/users/{user_id}") as response:
return await response.json()
# Running async functions
async def main():
async with aiohttp.ClientSession() as session:
user = await fetch_user(session, 42)
print(user)
asyncio.run(main())
# Concurrent execution (run multiple at once!)
async def fetch_all_users(user_ids: list[int]) -> list[dict]:
async with aiohttp.ClientSession() as session:
tasks = [fetch_user(session, uid) for uid in user_ids]
return await asyncio.gather(*tasks) # parallel!
# asyncio.gather vs sequential
# Sequential: 10 requests × 1s each = 10 seconds
# gather: 10 requests × 1s each = ~1 secondCommon Pitfalls
- async/await only provides concurrency for I/O-bound tasks. CPU-bound tasks (computation) still need multiprocessing.
- You cannot use await inside a regular (non-async) function — you'll get a SyntaxError.
- Interview tip: FastAPI is built on asyncio — all route handlers can be async def, enabling thousands of concurrent connections.
Real-World Example
A concurrent web scraper that fetches 100 URLs in parallel:
example
python
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def fetch_product_price(session: aiohttp.ClientSession, url: str) -> dict:
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
html = await resp.text()
soup = BeautifulSoup(html, "html.parser")
price = soup.select_one(".product-price")
return {"url": url, "price": price.text if price else "N/A"}
except Exception as e:
return {"url": url, "error": str(e)}
async def scrape_all_prices(urls: list[str]) -> list[dict]:
# Limit concurrent requests to avoid overwhelming servers
semaphore = asyncio.Semaphore(10)
async def bounded_fetch(url):
async with semaphore:
return await fetch_product_price(session, url)
async with aiohttp.ClientSession() as session:
tasks = [bounded_fetch(url) for url in urls]
return await asyncio.gather(*tasks)
results = asyncio.run(scrape_all_prices(product_urls))