【js篇】await 到底在等啥?—— 深度解析 JavaScript 中的“等待”本质

29 阅读4分钟

await 是现代 JavaScript 异步编程中最常见的关键字之一。我们常说“await 等待一个异步操作完成”,但这句话其实不够精确

本文将彻底揭开 await 的神秘面纱,告诉你它到底在等什么,以及背后的运行机制。


一、一个常见的误解

❌ “await 是在等待一个 async 函数执行完成。”

这个说法不准确。更精确的说法是:

await 等待的是一个表达式的计算结果,而这个结果可能是一个 Promise,也可能是一个普通值。


二、await 的真正作用

await 的本质:暂停函数执行,等待“值”的到来

await 会:

  1. 暂停当前 async 函数的执行;
  2. 等待右侧表达式的结果;
  3. 将结果作为 await 表达式的返回值;
  4. 恢复函数执行。

三、await 在等什么?—— 两种情况

✅ 情况 1:等待一个 非 Promise 值(同步值)

function getSomething() {
  return "something";
}

async function test() {
  const v1 = await getSomething(); // 等待 "something"
  console.log(v1); // 立即输出: something
}

test();

🧠 发生了什么?

  • getSomething() 返回 "something",一个普通字符串
  • await 发现它不是 Promise,于是立即继续执行
  • v1 = "something"
  • 函数恢复执行。

结论await 遇到非 Promise 值时,不会真正“等待”,而是立即返回该值。


✅ 情况 2:等待一个 Promise 对象

function testAsy(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 3000);
  });
}

async function testAwt() {
  let result = await testAsy('hello world'); // 等待 Promise resolve
  console.log(result);    // 3秒后输出: hello world
  console.log('cuger');   // 3秒后输出: cuger
}

testAwt();
console.log('cug'); // 立即输出: cug

🧠 发生了什么?

  1. testAwt() 被调用,返回一个 Promise
  2. 执行到 await testAsy('hello world')
    • testAsy() 返回一个 Promise(3秒后 resolve);
    • await 发现这是一个 Promise,于是暂停 testAwt 函数的执行
    • 控制权交还给事件循环;
  3. console.log('cug') 执行,立即输出;
  4. 3秒后,Promise resolve,事件循环将 testAwt 放回调用栈;
  5. testAwt 恢复执行:
    • result = 'hello world'
    • 输出 hello world
    • 输出 cuger

🔍 输出顺序:

cug        ← 立即执行
hello world ← 3秒后
cuger     ← 3秒后(与上一行几乎同时)

结论:只有当 await 右侧是 Promise 时,才会真正“等待”——即暂停函数执行,直到 Promise 状态改变。


四、await 的等价转换

await 的行为可以用 .then() 来模拟:

// 使用 await
async function fetchData() {
  const data = await fetch('/api/data').then(r => r.json());
  console.log(data);
}

// 等价于
function fetchDataLegacy() {
  return fetch('/api/data')
    .then(r => r.json())
    .then(data => {
      console.log(data);
    });
}

await expr 本质上是 expr.then(value => /* 恢复函数执行,value 是 await 的返回值 */) 的语法糖。


五、await 可以等什么?

表达式类型示例await 的行为
普通值await 42立即返回 42
函数调用(返回普通值)await getSomething()等待函数执行,立即返回结果
Promise 对象await fetch('/api')暂停函数,等待 resolve/reject
async 函数调用await asyncFunc()等待其返回的 Promise
直接量await 'hello'立即返回 'hello'
undefinedawait undefined立即返回 undefined
async function demo() {
  console.log(await 1);           // 1
  console.log(await Promise.resolve(2)); // 2(3秒后)
  console.log(await (async () => 3)());  // 3
  console.log(await { then: cb => cb(4) }); // 4(鸭子类型,类 Promise)
}

关键点await 会“解包”(unwrap)Promise,返回其 resolve 的值。


六、await 为什么必须在 async 函数中?

❌ 错误示例

// SyntaxError: await is only valid in async functions
const data = await fetch('/api/data');

✅ 原因

  • await 会暂停函数执行,这需要函数具有“可暂停”的特性;
  • 只有 async 函数被标记为可暂停,其执行上下文支持 await
  • 普通函数无法“暂停”,所以语法上禁止。

🔧 例外:顶层 await 在 ES 模块中是允许的(如 .mjs 文件或 <script type="module">)。

// 在模块中合法
const response = await fetch('/api/config');
const config = await response.json();
export { config };

七、await 的“阻塞”是相对的

await 不会阻塞整个程序

async function slowTask() {
  await new Promise(r => setTimeout(r, 5000));
  console.log('慢任务完成');
}

async function fastTask() {
  console.log('快任务开始');
  await new Promise(r => setTimeout(r, 1000));
  console.log('快任务完成');
}

slowTask();
fastTask();

// 输出:
// 快任务开始
// (1秒后)
// 快任务完成
// (4秒后)
// 慢任务完成

await 只暂停当前 async 函数,其他代码(包括其他 async 函数)可以正常执行。


八、深入理解:await 如何与事件循环交互

async function main() {
  console.log('A');
  await Promise.resolve();
  console.log('B');
}
console.log('C');
main();
console.log('D');

// 输出顺序:C → A → D → B

🧠 为什么是这个顺序?

  1. console.log('C') → 立即执行;
  2. main() 调用:
    • console.log('A') → 立即执行;
    • await Promise.resolve() → 虽然 Promise 已 resolve,但 await 会将其推入微任务队列;
    • 函数暂停;
  3. console.log('D') → 立即执行;
  4. 当前宏任务结束,事件循环处理微任务;
  5. main 函数恢复执行,console.log('B')

关键await 即使面对已 resolve 的 Promise,也会产生一次“微任务级”的暂停。


💡 结语

await 不是在等一个函数,而是在等一个值。”

  • 如果等的是普通值:立即返回,不等待;
  • 如果等的是Promise:暂停函数,等待 resolve;
  • await 的“等待”本质是控制权让渡,而非线程阻塞。

掌握这一点,你就能:

  • 正确理解 await 的执行时机;
  • 避免不必要的 await
  • 写出更高效的异步代码。