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.