网络异步请求和 async、await

理解 await 和异步网络请求,是掌握现代 JavaScript(以及 Python 的 asyncio 等高级特性)的绝对分水岭。

要弄懂它,我们不能只看代码,得先明白计算机在处理网络请求时到底面临什么问题。我们把这个过程拆解开来。


一、 为什么需要“异步”?(点餐的哲学)

假设你去快餐店点餐,有两种服务模式,这就对应了代码的两种执行方式:

  1. 同步模式(Synchronous - 阻塞):
    你点了一个汉堡,然后你必须死死站在收银台前等 10 分钟。在这 10 分钟里,你不能玩手机,不能找座位,后面的顾客也不能点餐。整个餐厅(程序)因为等你这个汉堡(网络请求)被完全卡死(阻塞)了。
  2. 异步模式(Asynchronous - 非阻塞):
    你点了一个汉堡,收银员给你一个取餐呼叫器(Promise)。然后你离开收银台去旁边找座位、刷手机(执行其他代码)。收银员也可以继续给后面的顾客点餐。等汉堡做好了,呼叫器一响(网络请求返回),你再去拿汉堡。

网络请求的本质,就是极度缓慢的 I/O(输入/输出)操作。
对于 CPU 来说,执行一行运算代码只需要几纳秒;但发送一个网络请求到 OpenAI 的服务器,再等服务器算完把结果传回来,可能需要好几秒。如果使用“同步模式”,整个程序在这几秒内会完全假死,用户的界面会卡住无法点击。因此,网络请求必须是异步的。


二、 异步网络请求的底层逻辑

当你执行之前那句调用大模型的代码时,底层发生了这样的事情:

  1. 交出任务: JavaScript 的主线程跑到这一行,发现这是一个网络请求,它自己不处理,而是把这个任务丢给底层的环境(比如浏览器的网络模块,或 Node.js 的 C++ 底层)。
  2. 获得凭证: 底层环境立刻丢给主线程一个“取餐呼叫器”。在编程语言里,这个呼叫器叫做 Promise(承诺)。它代表一个“目前还没结果,但未来某个时刻一定会给你个说法”的对象。
  3. 继续跑路: 主线程拿到 Promise 后,根本不留在原地等,而是立刻闪人,去执行后面的其他代码。
  4. 回调触发: 几秒钟后,底层的网络模块收到了 OpenAI 发来的数据,它会“按响呼叫器”。主线程在空闲时,就会接手这些数据,继续往下处理。

三、 await 语法的诞生与威力

在早期,为了处理那个“呼叫器响了之后该干嘛”的逻辑,程序员必须写出非常反人类的回调函数(Callback),或者冗长的 .then() 链式调用。

await 关键字的出现,被称为 JavaScript 异步编程的“终极语法糖”。 它让你可以用写同步代码的直观思维,来写异步代码。

await 的三大核心规则:

  1. JavaScript 发展过程中一个非常重要的新特性:顶层 await(Top-level await
    在早期的 JavaScript 标准中,await 必须严格包裹在一个由 async 关键字修饰的函数内部。如果在任何函数之外(即全局作用域/顶层)直接写 await,程序会直接报语法错误(SyntaxError: await is only valid in async functions)。

Node.js v14.8.0+ 以及现代前端打包工具(如 Vite、Webpack 5)开始,JavaScript 官方引入了 **Top-level await** 特性,启用了 ES 模块(ES Modules)的顶层 await

只要你的代码运行在 ES 模块环境 中,你就不再需要为了使用 await 而特意写一个用 async 包裹的匿名函数

  1. 它的作用是“暂停”和“等待”:
    当代码执行到 await client.responses.create(...) 时,这个 async 函数内部的执行会被瞬间暂停await 就像一个结界,它说:“在拿到网络请求的最终结果之前,这一行下面的代码谁也不许跑!”
  2. 它不会阻塞主线程(最神奇的地方):
    虽然当前函数被暂停了,但 JavaScript 会立刻跳出这个函数,去执行函数外面的其他代码。程序的整体运行没有被卡死!

四、 代码直观对比

我们来看看如果没有 await,获取大模型回答的代码长什么样:

【过去】使用 .then()(有些繁琐):

function getStory() {
  // 发起请求,返回一个 Promise
  client.responses.create({ input: "Write a story" })
    .then(response => {
        // 等请求成功后,这里的代码才执行
        console.log(response.output_text);
    });
    
  console.log("这行代码会先打印出来!");
}

【现在】使用 async / await(极其清晰):

async function getStory() {
  // 代码在这里暂停等待,但主线程没卡死
  const response = await client.responses.create({ input: "Write a story" });
  
  // 拿到结果后,直接往下走,就像普通变量赋值一样
  console.log(response.output_text);
}

核心洞察: await 的本质,就是帮你自动把网络请求返回的那个 Promise 里的数据“拆包”出来,直接赋值给左边的变量。它剥离了复杂的异步流程,让代码阅读起来就是一条直观的“从上到下”的直线。

posted @ 2026-07-31 15:47  立体风  阅读(5)  评论(0)    收藏  举报