await 是现代 JavaScript 异步编程中最常见的关键字之一。我们常说“await 等待一个异步操作完成”,但这句话其实不够精确。
本文将彻底揭开 await 的神秘面纱,告诉你它到底在等什么,以及背后的运行机制。
一、一个常见的误解
❌ “
await是在等待一个async函数执行完成。”
这个说法不准确。更精确的说法是:
✅
await等待的是一个表达式的计算结果,而这个结果可能是一个 Promise,也可能是一个普通值。
二、await 的真正作用
✅ await 的本质:暂停函数执行,等待“值”的到来
await 会:
- 暂停当前
async函数的执行; - 等待右侧表达式的结果;
- 将结果作为
await表达式的返回值; - 恢复函数执行。
三、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
🧠 发生了什么?
testAwt()被调用,返回一个Promise;- 执行到
await testAsy('hello world'):testAsy()返回一个Promise(3秒后 resolve);await发现这是一个Promise,于是暂停testAwt函数的执行;- 控制权交还给事件循环;
console.log('cug')执行,立即输出;- 3秒后,
Promiseresolve,事件循环将testAwt放回调用栈; 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' |
| undefined | await 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
🧠 为什么是这个顺序?
console.log('C')→ 立即执行;main()调用:console.log('A')→ 立即执行;await Promise.resolve()→ 虽然 Promise 已 resolve,但await会将其推入微任务队列;- 函数暂停;
console.log('D')→ 立即执行;- 当前宏任务结束,事件循环处理微任务;
main函数恢复执行,console.log('B')。
✅ 关键:
await即使面对已 resolve 的 Promise,也会产生一次“微任务级”的暂停。
💡 结语
“
await不是在等一个函数,而是在等一个值。”
- 如果等的是普通值:立即返回,不等待;
- 如果等的是Promise:暂停函数,等待 resolve;
await的“等待”本质是控制权让渡,而非线程阻塞。
掌握这一点,你就能:
- 正确理解
await的执行时机; - 避免不必要的
await; - 写出更高效的异步代码。