PyPyBridge

异步:从 Promise 到 asyncio

async/await 几乎同形,事件循环几乎同构 —— 你已有的并发直觉大部分直接平移。

14 分钟 · 右栏代码可点击「运行」


同一个并发抓取

JavaScript
1async function fetchOne(url) {
2 const res = await fetch(url);
3 return res.status;
4}
5
6const status = await Promise.all(
7 urls.map(fetchOne)
8);
Python
1import asyncio, httpx
2
3async def fetch_one(url):
4 async with httpx.AsyncClient() as client:
5 r = await client.get(url)
6 return r.status_code
7
8status = await asyncio.gather(
9 *(fetch_one(u) for u in urls)
10)
  • 本例需要 httpx 与网络,游乐场跑不了;下一张卡里有一个可运行的纯 asyncio 版本。
  • Promise.allasyncio.gatherPromise.raceasyncio.wait(..., return_when=FIRST_COMPLETED)

可运行的纯 asyncio 对照

JavaScript
1const sleep = ms =>
2 new Promise(r => setTimeout(r, ms));
3
4async function task(name, s) {
5 await sleep(s * 1000);
6 return name + " done";
7}
8
9const out = await Promise.all([
10 task("a", 1), task("b", 2),
11]);
Python
1import asyncio
2
3async def task(name, s):
4 await asyncio.sleep(s)
5 return name + " done"
6
7async def main():
8 out = await asyncio.gather(
9 task("a", 1), task("b", 2)
10 )
11 print(out)
12
13asyncio.run(main()) # 显式入口,JS 没有这一步
  • 语法差异只有两处:async functionasync def,以及顶层 await 在脚本里不合法,需要 asyncio.run(main()) 包一层(Jupyter 里顶层 await 直接可用)。

注意

两个 JS 没有的坑① 忘了 await 的协程不会执行,只会得到一条警告 —— 比 unhandled rejection 更安静。② 事件循环里任何同步阻塞调用(time.sleep、大计算、同步 IO)会冻住一切,和 JS 相同但没有微任务宏任务的缓冲说法,别混用同步库。

练一练

这张卡的示例代码已在游乐场中备好,改一改再运行。

在游乐场中打开