跳到主要内容

async/await:让异步看起来像同步

殊途同归

终于到了这一章的终极武器——async/await

还记得餐厅的故事吗?

  • 回调:服务员口头通知你,容易搞混
  • Promise:给你取餐号,但你得一直盯着号码牌看
  • async/await:你坐在座位上,菜来了服务员直接端上来,你什么都不用想

用了 async/await,异步代码写起来就像同步代码一样自然——该等就等,但程序不会卡死

最有意思的是:Python 和 JavaScript 在这里使用了完全相同的关键字asyncawait。它们各自走了不同的路,最终殊途同归。

基本语法

加载代码编辑器中……

两边的结构几乎一模一样:

PythonJavaScript
声明异步函数async def func():async function func() {}
等待异步操作await somethingawait something
模拟等待await asyncio.sleep(1)await new Promise(r => setTimeout(r, 1000))
启动入口asyncio.run(main())main()(顶层可以直接调用)

没错,asyncawait 这两个关键字完全一样!这大概是 Python 和 JS 最像的地方了。

async/await 本质上是 Promise 的语法糖

在 JS 里,async 函数自动返回一个 Promise,而 await 就是等待一个 Promise 完成。换句话说,它只是让你用更舒服的方式写 Promise 代码:

加载代码编辑器中……

用 async/await 改写"回调地狱"

还记得前两节那个"查用户 → 查订单 → 查商品"的例子吗?用 async/await 改写后,代码终于变得和 Python 一样清爽了:

加载代码编辑器中……

三个版本的进化:

版本代码风格可读性
回调层层嵌套 })})})头疼
Promise.then().then().then()凑合
async/await一行一行,像同步舒服

错误处理:try/catch

在异步代码中处理错误也变得直觉了——直接用 try/catch(JS)或 try/except(Python):

加载代码编辑器中……

对比 Promise 的 .catch()try/catch 更像正常的同步错误处理——你写过同步的 try/catch,异步的也会了。

踩坑提醒

忘记写 await 是最常见的 async 错误!

async function main() {
const data = fetchData() // 忘了 await!
console.log(data) // 输出: Promise { <pending> }
}

不加 await,你拿到的是一个 Promise 对象,而不是它的结果。就像去取餐时只拿走了号码牌,却没有拿走菜。

Python 也一样——调用 async 函数不加 await 会得到一个协程对象,而且 Python 还会给你一个警告:RuntimeWarning: coroutine 'xxx' was never awaited

并行执行:同时做多件事

await 一个接一个地等,是串行的。但有时候你想同时发多个请求——比如同时查询三个餐厅的菜单:

加载代码编辑器中……
PythonJavaScript
并行等所有asyncio.gather(task1, task2)Promise.all([task1, task2])
参数形式逐个传入传入数组

事件循环:幕后大管家

你可能好奇:await 的时候程序在干什么?答案是——事件循环(Event Loop) 在幕后调度一切。

把事件循环想象成餐厅里的大堂经理

  1. 他手里有一张待办清单(任务队列)
  2. 他依次处理清单上的事情
  3. 遇到要等的事(比如等厨房),他不会傻等,而是先去处理清单上的下一件事
  4. 厨房做好了会通知他,他再把"上菜"这件事加回清单
┌─────────────────────────────────────┐
│ 事件循环 │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 任务1 │→ │ 任务2 │→ │ 任务3 │ │
│ └───────┘ └───────┘ └───────┘ │
│ ↑ │ │
│ └─────────────────────┘ │
│ 循环处理 │
└─────────────────────────────────────┘
↕ ↕
┌─────────┐ ┌─────────┐
│ 网络请求 │ │ 定时器 │
│ (等待中) │ │ (等待中) │
└─────────┘ └─────────┘

关键区别:

  • JS 的事件循环是内置的、自动运行的。浏览器和 Node.js 天生就有事件循环——你写的每一行 JS 代码都在事件循环里。
  • Python 的事件循环需要你手动启动:asyncio.run(main())。Python 的默认模式是同步的,异步是"可选升级"。
问问 AI

问 AI:"并发(concurrency)和并行(parallelism)有什么区别?async/await 实现的是哪一种?"

这是一个经典面试题。简单来说:

  • 并发:一个人同时处理多件事(不停切换注意力)
  • 并行:多个人同时做不同的事

async/await 实现的是并发——只有一个线程,但它不会傻等。理解这个区别很重要。

一个接近真实的例子

让我们用 async/await 写一个更贴近实际的例子——模拟同时从多个 API 获取数据:

加载代码编辑器中……
🏋️AI 练习

写一个"赛跑点餐"程序:

  1. 模拟 3 家外卖平台同时下单(每家配送时间随机 1-5 秒)
  2. 哪家先送到就先吃哪家(用 Promise.raceasyncio.waitFIRST_COMPLETED
  3. 打印出是哪家平台最先送达,以及用了多少时间

Python 和 JS 各写一版。提示:JS 用 Promise.race,Python 用 asyncio.wait 配合 return_when=asyncio.FIRST_COMPLETED

可以让 AI 帮你生成初始框架,然后自己修改完善。

完整对照表

概念PythonJavaScript
声明异步函数async def func():async function func() {}
等待异步结果await exprawait expr
启动事件循环asyncio.run(main())自动(浏览器/Node.js 内置)
并行等待asyncio.gather(a, b, c)Promise.all([a, b, c])
错误处理try/excepttry/catch
抛出错误raise ValueError(...)throw new Error(...)
暂停等待await asyncio.sleep(n)await new Promise(r => setTimeout(r, n))

小结

一句话总结异步编程的进化史:

回调 → 能用,但嵌套太深(回调地狱)

Promise → 链式调用,拍平了嵌套(JS 专属)

async/await → 看起来像同步,写起来最舒服(两种语言殊途同归)

Python 和 JavaScript 在异步编程上最终选择了相同的语法。这说明 async/await 确实是目前最好的异步编程范式——简洁、直观、易于理解。

学完这一章,你已经掌握了现代编程中最重要的概念之一。下次看到网络请求卡住了、界面转圈圈了,你就知道——该请出 async/await 了。