You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JS中await、Promise与setTimeout的代码执行优先级解析

JavaScript中await、Promise回调与setTimeout的执行优先级规则

我在梳理JavaScript中包含await时的代码执行顺序时遇到了困惑,以下是两个测试示例及问题:

示例1

const a = Promise.resolve(98);
const b = Promise.resolve(78);
const c = Promise.resolve(45);
const e = Promise.resolve(23);
setTimeout(() => console.log("hi8"));
const d = c.then(() => {
    console.log("hi9")
});
console.log("hi0");
async function lome() {
    console.log("hi1");
    await setTimeout(() => console.log("hi7"));
    console.log("hi2");
}
lome2();
lome();
console.log("hi3");

async function lome2() {
    console.log("hi10");
    await e.then(() => {
        console.log("hi11")
    });
    console.log("hi12");
}

setTimeout(() => console.log("hi4"));

b.then(() => console.log("hi6"));

输出顺序:

hi0
hi10
hi1
hi3
hi9
hi11
hi7
hi6
hi12
hi2
hi8
hi4

从这个输出我最初推测:同步代码优先执行,接着是Promise回调,然后是await后的代码,最后是setTimeout回调——即await后代码优先级高于setTimeout、低于Promise回调。

示例2

const a = Promise.resolve(98);
const b = Promise.resolve(78);
const c = Promise.resolve(45);
const e = Promise.resolve(23);
setTimeout(() => console.log("hi8"));
const d = c.then(() => {
    console.log("hi9")
});
console.log("hi0");
async function lome() {
    console.log("hi1");
    await setTimeout(() => console.log("hi7"));
    console.log("hi2");
}
lome2();
lome();


async function lome2() {
    console.log("hi10");
    await e.then(() => {
        console.log("hi11")
    });
    console.log("hi12");
}

setTimeout(() => console.log("hi4"));
b.then(() => console.log("hi6"));
console.log("hi3");

输出顺序:

hi0
hi10
hi1
hi3
hi9
hi11
hi2
hi6
hi12
hi8
hi7
hi4

但这个示例打破了之前的推测:同步代码执行后,Promise回调(d和lome2中的回调)执行完毕,本该执行b的Promise回调输出hi6,却先输出了lome函数中await后的hi2。


核心规则:事件循环中的微任务与宏任务

JavaScript的执行顺序本质由事件循环决定,任务分为两类,优先级从高到低:

  1. 同步代码:立即执行,占据主线程;
  2. 微任务(Microtasks):同步代码执行完后立即处理,包括:
    • Promise的then/catch/finally回调;
    • async/await中await后的代码(本质是Promise的then回调);
    • queueMicrotask创建的任务;
  3. 宏任务(Macrotasks):微任务队列清空后才会处理,包括:
    • setTimeout/setInterval回调;
    • DOM事件回调;
    • I/O操作回调;

关键细节:await的本质

await x的执行逻辑可以拆解为:

  1. 先同步执行x的代码;
  2. 如果x不是Promise,JavaScript会自动将其包装为Promise.resolve(x);
  3. 将await之后的代码包装成一个微任务,加入微任务队列;
  4. 暂停当前async函数,让出主线权,继续执行后续同步代码。

注意:如果x是一个已决议的Promise(比如Promise.resolve(xxx)),那么await后的微任务会立即被加入队列;如果x是一个未决议的Promise,那么要等Promise决议后,才会将后续代码加入微任务队列。

示例2的执行流程拆解

我们一步步理清楚示例2的执行顺序:

1. 同步代码阶段

  • 定义所有Promise变量(均为已决议状态);
  • 注册setTimeout(hi8)宏任务;
  • 注册c.then(hi9)微任务(因为c已决议,直接加入队列);
  • 输出hi0;
  • 调用lome2():
    • 输出hi10;
    • 执行await e.then(hi11):先注册e.then(hi11)微任务,await等待这个e.then返回的Promise决议,此时hi12的微任务暂未加入队列;
    • 暂停lome2,回到同步代码;
  • 调用lome():
    • 输出hi1;
    • 同步执行setTimeout(hi7),注册hi7宏任务;
    • await的是Promise.resolve(定时器ID)(因为setTimeout返回数字),这个Promise立即决议,所以console.log(hi2)的微任务被加入队列;
    • 暂停lome,回到同步代码;
  • 注册setTimeout(hi4)宏任务;
  • 注册b.then(hi6)微任务(b已决议,直接加入队列);
  • 输出hi3;

2. 微任务队列处理阶段

同步代码执行完毕后,微任务队列的初始顺序是:c.then(hi9) → e.then(hi11) → hi2 → b.then(hi6)

  • 执行c.then(hi9),输出hi9;
  • 执行e.then(hi11),输出hi11;此时e.then返回的Promise决议,hi12的微任务被加入队列;
  • 执行hi2,输出hi2;
  • 执行b.then(hi6),输出hi6;
  • 执行hi12,输出hi12;

3. 宏任务队列处理阶段

微任务队列清空后,开始处理宏任务队列,顺序是:hi8 → hi7 → hi4,依次输出对应的内容。

这就完全匹配了示例2的输出顺序,而之前的错误推测是因为没搞清楚await后的代码是微任务,以及微任务队列的添加顺序。

关于示例1输出的说明

示例1的输出中hi7(宏任务)出现在hi6(微任务)之前,这不符合事件循环规则,大概率是输出记录错误。按照正确逻辑,hi6作为微任务,应该在所有宏任务(包括hi7)之前执行。


内容的提问来源于stack exchange,提问作者lome1446

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:15:00