Python's asyncio as a JS Dev
Today I took a closer look at Python's asyncio from my JavaScript perspective. Overall, it's feels weirdly familiar given Node's async / await, but also has some quirky differences.
Event Loop
Both Python and Node manage concurrency with an event loop. Here's a quick mapping:
| Concept | Node.js | Python |
|---|---|---|
| Event loop | Built-in (node script.js) |
asyncio.run(main()) (Python +3.7) |
| Access event loop | Rarely needed | asyncio.get_running_loop() |
| Nested event loops | Not a concern | Can't nest calls to asyncio.run() |
Typically, Python 3.7+ encourages using asyncio.run() to manage everything neatly.
Coroutines ≈ JS Async Functions
Coroutines in Python are essentially like JavaScript's async functions:
import asyncio
async def fetch_url(url):
print(f'Starting to fetch {url}')
await asyncio.sleep(1)
print(f'Finished fetching {url}')
return f'{url} - {len(url)} bytes'
Just like JS, using await gives control back to the event loop until the coroutine finishes.
Running Coroutines
Single coroutine:
asyncio.run(fetch_url('https://andypai.me'))
Multiple concurrent coroutines (like Promise.all):
async def main():
await asyncio.gather(
fetch_url('https://til.andypai.me'),
fetch_url('https://neuroflow.andypai.me'),
fetch_url('https://tensorflowjs.andypai.me')
)
asyncio.run(main())
Background Tasks (basically setInterval)
To run something repeatedly in the background:
async def do_ticks():
while True:
print('Tick')
await asyncio.sleep(5)
async def main():
asyncio.create_task(do_ticks())
await asyncio.sleep(12)
asyncio.run(main())
Structured Concurrency (TaskGroup in Python 3.11+)
Python 3.11 introduced structured concurrency, similar to JS's Promise.allSettled:
async def main():
async with asyncio.TaskGroup() as tg:
for url in urls:
tg.create_task(fetch_url(url))
If one task errors, it neatly cancels the others and raises an ExceptionGroup.
Python/JS Async Equivalents Cheat Sheet
| JavaScript | Python asyncio |
|---|---|
Promise.all |
asyncio.gather() |
Promise.race |
asyncio.wait(return_when=FIRST_COMPLETED) |
setTimeout(fn, 0) |
loop.call_soon(fn) |
| Worker threads (CPU tasks) | loop.run_in_executor() or multiprocessing |
| Canceling tasks | task.cancel() or asyncio.TimeoutError |
Quick Example: Async HTTP Requests
Python's async HTTP client (aiohttp) feels similar to Node's fetch API:
import asyncio, aiohttp
URLS = [
'https://finbox.io/1',
'https://finbox.io/2',
'https://finbox.io/3'
]
async def fetch(session, url):
async with session.get(url) as resp:
print(url, resp.status)
return await resp.text()
async def main():
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*(fetch(session, url) for url in URLS))
print('Total bytes:', sum(len(r) for r in results))
asyncio.run(main())
JavaScript equivalent (roughly):
const fetch = require('node-fetch')
await Promise.all(urls.map(u => fetch(u)))
So yeah, Javascript is way cleaner for async and io imho.