V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
autumnshine
V2EX  ›  Node.js

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

  •  1
     
  •   autumnshine · 2 天前 · 1798 次点击

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

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

    https://github.com/nodejs/node/issues/47319
    autumnshine
        12
    autumnshine  
    OP
       2 天前
    @vace 感谢大佬提供的信息源,谢谢。
    jinlongguo
        13
    jinlongguo  
       2 天前
    这是个很好的问题!你遇到的情况确实和传统的 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  
       2 天前
    前端都死了个球了,还玩这个八股文呢。
    Wxh16144
        15
    Wxh16144  
       1 天前
    @jinlongguo 请不要在回答技术问题时复制粘贴 AI 生成的内容
    h503mc
        16
    h503mc  
       1 天前 via Android
    @Livid #13 AI
    Livid
        17
    Livid  
    MOD
    PRO
       1 天前
    @Wxh16144
    @h503mc

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

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

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

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

    > 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  
       1 天前
    @Livid #26 能理解这种考虑,但是目前这个规则似乎很多人都不知道,如果有人无意中贴了 AI 回复的内容,就直接封号感觉有点过于粗暴了。能不能考虑回复框的 placeholder 里提示禁止复制大段 AI 回复,或者给一个选项勾选是否为 AI 回复的内容,然后做一些折叠相关的策略。
    shizhihuaxu
        29
    shizhihuaxu  
       9 小时 56 分钟前
    @AmiKara 赞同
    shizhihuaxu
        30
    shizhihuaxu  
       9 小时 41 分钟前
    @shizhihuaxu 爆粗口的不封,13 楼虽然引用了 ai 的内容回复,但是是技术讨论范畴的,并非水文,如果是来源于 ai 的回复,又不标明,就可以被认可了?
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   1151 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 17:50 · PVG 01:50 · LAX 09:50 · JFK 12:50
    ♥ Do have faith in what you're doing.