关于 Node.js 中的事件循环问题。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
autumnshine
V2EX    Node.js

关于 Node.js 中的事件循环问题。

  •  1
     
  •   autumnshine 12 天前 2162 次点击

    大佬们,小弟最近在学习 Node.js ,发现 Promise.then 中的代码会先于 process.nextTick 执行,网上资料普遍说的是 process.nextTick 会先于微任务执行,请问这是什么原因...

    有如下代码:

    console.log("script start"); setTimeout(() => { console.log("setTimeout"); }, 0); process.nextTick(() => console.log("nextTick")); new Promise((resolve, reject) => { console.log("promise1"); resolve(undefined); console.log("promise2"); }).then(() => { console.log("promise3"); }); console.log("script end"); 

    执行结果为:

    script start promise1 promise2 script end promise3 // 为什么会先于输出这个而不是 nextTick ?? nextTick setTimeout 

    直接使用 Node.js 执行 ts 文件,代码执行环境: Node:v25.2.1 TypeScript:5.9.3

    tsconfig.json

    { "compilerOptions": { "target": "ESNext", "module": "ESNext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "noImplicitAny": true, "noImplicitReturns": true, "strictNullChecks": true } } 
    第 1 条附言    12 天前
    根据 #11 @vace 大佬提供的信息源,里面回复已经说明问题的由来。因此,已经得到满意的答案了。
    30 条回复    2025-12-04 16:08:50 +08:00
    ntedshen
        1
    ntedshen  
       12 天前
    https://nodejs.org/en/learn/asynchronous-work/understanding-processnexttick
    没问题吧,你现在实际上不就只有 setTimeout 在异步。。。
    rabbbit
        2
    rabbbit  
       12 天前
    node 直接执行试了下,输出如下
    script start
    promise1
    promise2
    script end
    nextTick
    promise3
    setTimeout
    autumnshine
        3
    autumnshine  
    OP
       12 天前
    补充一下信息:package.json 中的"type": "module"。
    autumnshine
        4
    autumnshine  
    OP
       12 天前
    autumnshine
        5
    autumnshine  
    OP
       12 天前
    @rabbbit 麻烦看看我回复的图片(捂脸。。。
    autumnshine
        6
    autumnshine  
    OP
       12 天前
    @ntedshen 有 Promise.then 。
    rabbbit
        7
    rabbbit  
       12 天前
    @autumnshine 看不到图片,我的梯子 ip 被 imgur 屏蔽了返回 403
    autumnshine
        8
    autumnshine  
    OP
       12 天前
    rabbbit
        9
    rabbbit  
       12 天前
    试了下 node 25 ,看来这块有改动。

    script start
    promise1
    promise2
    script end
    promise3
    nextTick
    setTimeout
    ntedshen
        10
    ntedshen  
       12 天前
    node20 也一样,在 type:module 和 type:commonjs 的情况下返回的顺序不同。。。
    这应该是 esm 和 cjs 规范上的区别吧,感觉可能要去代码里翻了。。。
    vace
        11
    vace  
       12 天前   7
    主要是 ESM 和 CJS 模块的解析差异造成的,CJS 是立即执行,ESM 模块的入口模块是一个异步 job ,此时先进入 microtask 所以先输出了 promise3 。

    https://github.com/nodejs/node/issues/47319
    autumnshine
        12
    autumnshine  
    OP
       12 天前
    @vace 感谢大佬提供的信息源,谢谢。
    jinlongguo
        13
    jinlongguo  
       12 天前
    这是个很好的问题!你遇到的情况确实和传统的 Node.js 事件循环理解不一样。关键原因在于 ES Module ( ESM )和 CommonJS 的执行差异。
    问题根源
    你的 tsconfig.json 配置了 "module": "ESNext",这意味着代码会以 ES 模块格式运行。在 Node.js 中:

    CommonJS 模式:process.nextTick 确实优先于 Promise 微任务
    ES Module 模式:微任务队列的处理时机不同,Promise 可能先于 nextTick 执行

    验证方法
    你可以做个对比实验:
    1. CommonJS 版本(.js 文件)
    js// test.js
    console.log("script start");

    setTimeout(() => {
    console.log("setTimeout");
    }, 0);

    process.nextTick(() => console.log("nextTick"));

    new Promise((resolve) => {
    console.log("promise1");
    resolve();
    console.log("promise2");
    }).then(() => {
    console.log("promise3");
    });

    console.log("script end");
    ```

    运行 `node test.js`,你会看到:
    ```
    script start
    promise1
    promise2
    script end
    nextTick // nextTick 先执行
    promise3
    setTimeout
    2. 修改你的 TypeScript 配置
    将 tsconfig.json 改为 CommonJS:
    json{
    "compilerOptions": {
    "target": "ESNext",
    "module": "CommonJS", // 改这里
    "esModuleInterop": true,
    // ...其他配置
    }
    }
    为什么会这样?
    在 ES Module 中,模块的顶层代码本身就在一个微任务中执行,这会影响 process.nextTick 和 Promise 的相对顺序。Node.js 在处理 ESM 时,会在模块评估期间使用不同的微任务调度策略。
    建议

    如果需要严格控制执行顺序,使用 CommonJS 模式
    如果必须使用 ESM ,理解这种行为差异,或者考虑使用 setImmediate 等其他 API
    最佳实践:不要依赖 nextTick 和 Promise 之间的精确执行顺序,因为这在不同环境下可能不一致

    你可以尝试修改配置后重新运行,应该就能看到符合预期的执行顺序了!

    ----答案来自 claude
    chouvel
        14
    chouvel  
       12 天前
    前端都死了个球了,还玩这个八股文呢。
    Wxh16144
        15
    Wxh16144  
       12 天前
    @jinlongguo 请不要在回答技术问题时复制粘贴 AI 生成的内容
    h503mc
        16
    h503mc  
       12 天前 via Android
    @Livid #13 AI
    Livid
        17
    Livid  
    MOD
    PRO
       12 天前
    @Wxh16144
    @h503mc

    谢谢,13 楼那个大段复制张贴 AI 生成文本的账号已经被彻底 ban 。
    tonytonychopper
        18
    tonytonychopper  
       12 天前
    tonytonychopper
        19
    tonytonychopper  
       12 天前
    @tonytonychopper 发现处理了,请忽略我
    visper
        20
    visper  
       12 天前
    其实我觉得没必要分什么宏任务微任务,知道是同步异步就行了。谁敢靠这样的顺序来保证代码逻辑的,直接打死。
    hi2hi
        21
    hi2hi  
       12 天前
    @visper 找到原因是为了方便出现问题的时候 Debug ,正经开发要靠协定的规范,
    visper
        22
    visper  
       12 天前
    @hi2hi 通常来说,一找 bug 的时候发现到这个地方这样写了。我都不会去找原因了,直接改写法。
    mizuki9
        23
    mizuki9  
       12 天前
    这种行为差异,也代表 nodejs 永远不可能统一 cjs 与 esm 。在 nodejs 中,cjs 解析比 esm 快,nodejs 的 esm 的解析好像是在 cjs 上修修补补支持的。他们为了兼容历史代码,cjs 是永远首要支持。结果就是 cjs 生态不太可能转换为 esm ,也许十几、二十年后才有可能吧
    AmiKara
        24
    AmiKara  
       12 天前
    @Livid 想问下为什么复制 AI 回答会被封禁,他已经在底部备注了回答来自于 claude
    FishBear
        25
    FishBear  
       12 天前
    @AmiKara 这里不允许黏贴 AI 答案
    Livid
        26
    Livid  
    MOD
    PRO
       12 天前
    @AmiKara

    我及很多其他用户,都希望这里的回复区里这种大段的 AI 生成文本的出现概率接近 0 。

    不想在回复区看到这种东西。

    会一直坚持清理。
    superhot
        27
    superhot  
       12 天前
    这里有解释:

    > This is because the ES Module being loaded is wrapped as an asynchronous operation, and thus the entire script is actually already in the promises microtask queue. So when the promise is immediately resolved, its callback is appended to the microtask queue. Node.js will attempt to clear the queue until moving to any other queue, and hence you will see it outputs bar first.

    https://nodejs.org/en/learn/asynchronous-work/understanding-setimmediate
    AmiKara
        28
    AmiKara  
       12 天前
    @Livid #26 能理解这种考虑,但是目前这个规则似乎很多人都不知道,如果有人无意中贴了 AI 回复的内容,就直接封号感觉有点过于粗暴了。能不能考虑回复框的 placeholder 里提示禁止复制大段 AI 回复,或者给一个选项勾选是否为 AI 回复的内容,然后做一些折叠相关的策略。
    shizhihuaxu
        29
    shizhihuaxu  
       11 天前
    @AmiKara 赞同
    shizhihuaxu
        30
    shizhihuaxu  
       11 天前
    @shizhihuaxu 爆粗口的不封,13 楼虽然引用了 ai 的内容回复,但是是技术讨论范畴的,并非水文,如果是来源于 ai 的回复,又不标明,就可以被认可了?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3215 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 11:19 PVG 19:19 LAX 03:19 JFK 06:19
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86