javascript 中 async 函数通过 promise 机制将异步操作挂起,其调度优先级由事件循环(event loop)的宏任务与微任务队列决定。async 函数内部遇到 await 时,会将后续代码包装为微任务并立即返回一个未完成的 promise,从而让出执行权给同步代码或其他宏任务;当 await 后的 promise 状态变为 fulfilled 或 rejected 时,该剩余部分会被推入微任务队列,在下一个微任务阶段优先于所有宏任务执行。因此,async/await 并不改变 javascript 单线程的执行模型,而是利用微任务的高优先级实现非阻塞的顺序逻辑。

await 关键字如何触发微任务调度
await 关键字的作用是暂停 async 函数的执行,并将剩余的代码逻辑注册到微任务队列中。当表达式求值结果为 Promise 对象时,引擎会调用 .then() 方法,将回调函数加入微任务队列。这意味着,无论 await 等待的是 I/O 操作、定时器还是另一个 async 函数,其恢复执行的时机都严格遵循微任务优先原则。在当前宏任务(如点击事件、脚本主流程)结束前,所有已排队的微任务都会依次执行完毕,之后才会进入下一个宏任务的检查阶段。

async function fetchData() {
console.log('Start');
const result = await new Promise(resolve => {
setTimeout(() => resolve('Data'), 0);
});
console.log('End', result);
}
fetchData();
console.log('Script End');
上述代码的运行结果顺序为:Start、Script End、End Data。这证明了 await 之后的代码被推迟到了当前同步代码块执行完之后,作为微任务处理。即使 setTimeout 延迟为 0,它本身是宏任务,但其 resolved 触发的回调属于微任务范畴(由 Promise 机制决定),因此在同步代码结束后立即执行。
立即学习“Java免费学习笔记(深入)”;
多个 async 函数并发时的执行顺序
当多个 async 函数同时运行时,它们的初始同步部分会按声明顺序立即执行,而各自的 await 点会将各自剩余的逻辑独立地推入微任务队列。如果这些 async 函数之间没有依赖关系,它们可以并行发起请求,但恢复执行的顺序取决于 Promise 解析的先后以及微任务队列的 FIFO(先进先出)特性。若存在串行依赖,即后一个 async 函数需要前一个的结果,则必须使用 await 显式等待,此时后者的微任务注册会延迟到前者完成之后,从而保证执行顺序。

async function taskA() {
console.log('A1');
await new Promise(r => setTimeout(r, 100));
console.log('A2');
}
async function taskB() {
console.log('B1');
await new Promise(r => setTimeout(r, 50));
console.log('B2');
}
taskA();
taskB();
console.log('Main');
运行结果顺序为:A1、B1、Main、B2、A2。虽然 taskA 先启动,但 taskB 的 Promise 在 50ms 后先 resolved,因此 B2 对应的微任务先入队并先执行,随后 A2 的微任务才执行。这体现了微任务队列的动态插入和顺序执行特性,而非简单的先来后到。
await 在非 async 函数中的行为差异
如果在非 async 函数中直接使用 await 关键字,会导致语法错误(SyntaxError)。这是因为 await 只能在 async 函数体内使用,这是 ECMAScript 规范的限制。若需要在普通函数中处理异步结果,必须返回 Promise 并使用 .then() 链式调用,或者将该普通函数包装在一个 async 函数中。这种限制确保了异步状态的追踪始终绑定在特定的 async 上下文栈中,避免了回调地狱和状态管理的混乱。开发者应确保所有涉及 await 的逻辑都封装在 async 函数内,并通过顶层 await(在模块环境中)或入口 async 函数来驱动整个异步流程。


















