<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Node.js on 吾爱主机</title><link>https://blog.waihost.com/categories/node.js/</link><description>Recent content in Node.js on 吾爱主机</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Thu, 23 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.waihost.com/categories/node.js/index.xml" rel="self" type="application/rss+xml"/><item><title>Node.js 事件循环与 nextTick / microtask：原理、阶段与工程实践</title><link>https://blog.waihost.com/posts/nodejs-event-loop-nexttick-microtask/</link><pubDate>Thu, 23 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/nodejs-event-loop-nexttick-microtask/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/nodejs-event-loop-nexttick-microtask.svg" alt="Featured image of post Node.js 事件循环与 nextTick / microtask：原理、阶段与工程实践" /&gt;&lt;p&gt;HTTP 服务偶发“全站卡住几秒”、定时器比预期晚很多、&lt;code&gt;setTimeout(0)&lt;/code&gt; 和 &lt;code&gt;setImmediate&lt;/code&gt; 顺序飘忽不定、同步校验回调却读到未初始化变量——这些现象往往不是“Node 慢”，而是对 &lt;strong&gt;事件循环（Event Loop）阶段、nextTick 队列、微任务队列、Worker Pool&lt;/strong&gt; 理解不到位。&lt;/p&gt;
&lt;p&gt;本文基于 Node.js 官方 Learn 文档 &lt;em&gt;The Node.js Event Loop&lt;/em&gt;、&lt;em&gt;Don&amp;rsquo;t Block the Event Loop&lt;/em&gt;，以及 API 文档 &lt;code&gt;process.nextTick&lt;/code&gt; / Timers，把调度机制讲清楚，并给出可复现实验与排障清单。&lt;/p&gt;
&lt;h2 id="一问题背景单线程-js-为何能扛大量并发"&gt;&lt;a href="#%e4%b8%80%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e5%8d%95%e7%ba%bf%e7%a8%8b-js-%e4%b8%ba%e4%bd%95%e8%83%bd%e6%89%9b%e5%a4%a7%e9%87%8f%e5%b9%b6%e5%8f%91" class="header-anchor"&gt;&lt;/a&gt;一、问题背景：单线程 JS 为何能扛大量并发
&lt;/h2&gt;&lt;p&gt;官方对事件循环的定位很直接：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;The event loop is what allows Node.js to perform &lt;strong&gt;non-blocking I/O&lt;/strong&gt; operations — despite the fact that a single JavaScript thread is used by default — by offloading operations to the system kernel whenever possible.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;也就是说：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;应用层 JS 默认跑在一条线程&lt;/strong&gt;（Event Loop / main thread）。&lt;/li&gt;
&lt;li&gt;能丢给内核的非阻塞 I/O（如多数网络 I/O）完成后，内核通知 Node，相关回调进入 &lt;strong&gt;poll&lt;/strong&gt; 等队列等待执行。&lt;/li&gt;
&lt;li&gt;不便做成纯非阻塞的昂贵工作（部分 &lt;code&gt;fs&lt;/code&gt;、&lt;code&gt;dns.lookup&lt;/code&gt;、&lt;code&gt;crypto.pbkdf2&lt;/code&gt;、&lt;code&gt;zlib&lt;/code&gt; 等）会提交到 libuv 的 &lt;strong&gt;Worker Pool&lt;/strong&gt;，做完再回到事件循环。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Don&amp;rsquo;t Block the Event Loop&lt;/em&gt; 给出的工程拇指法则是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Node.js is fast when the work associated with each client at any given time is &lt;strong&gt;&amp;ldquo;small&amp;rdquo;&lt;/strong&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;线程少、上下文切换少，是 Node 能用较少资源服务大量连接的原因；代价是：&lt;strong&gt;任意一次过长的同步回调，都会让其他客户端得不到轮转&lt;/strong&gt;——既伤吞吐，也构成 DoS 面（恶意大输入、灾难性正则等）。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;线程类型&lt;/th&gt;
					&lt;th&gt;跑什么&lt;/th&gt;
					&lt;th&gt;阻塞后果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Event Loop&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;初始化、JS 回调、&lt;code&gt;await&lt;/code&gt;/&lt;code&gt;then&lt;/code&gt; 续体、网络等非阻塞 I/O 编排&lt;/td&gt;
					&lt;td&gt;所有请求排队；定时器/I/O 回调整体延迟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Worker Pool&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;昂贵任务（多数 &lt;code&gt;fs&lt;/code&gt;、部分 DNS/Crypto/Zlib、C++ 提交的任务）&lt;/td&gt;
					&lt;td&gt;池子被占满时同类任务排队；回压到业务&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="二核心模型阶段队列--阶段外的-nexttick"&gt;&lt;a href="#%e4%ba%8c%e6%a0%b8%e5%bf%83%e6%a8%a1%e5%9e%8b%e9%98%b6%e6%ae%b5%e9%98%9f%e5%88%97--%e9%98%b6%e6%ae%b5%e5%a4%96%e7%9a%84-nexttick" class="header-anchor"&gt;&lt;/a&gt;二、核心模型：阶段队列 + 阶段外的 nextTick
&lt;/h2&gt;&lt;h3 id="1-启动与相位图"&gt;&lt;a href="#1-%e5%90%af%e5%8a%a8%e4%b8%8e%e7%9b%b8%e4%bd%8d%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;1. 启动与相位图
&lt;/h3&gt;&lt;p&gt;进程启动后会：初始化事件循环 → 执行入口脚本（其中可调异步 API、定时器、&lt;code&gt;process.nextTick()&lt;/code&gt;）→ 再进入循环迭代。&lt;/p&gt;
&lt;p&gt;官方简化相位（每个 box 是一个 &lt;strong&gt;phase&lt;/strong&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ timers │ setTimeout / setInterval
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─────────────┬─────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌─&amp;gt;│ pending callbacks │ 延迟到下一轮的部分系统 I/O 回调
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └─────────────┬─────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ idle, prepare （内部使用）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ poll 取新 I/O 事件；执行 I/O 回调
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ check setImmediate 回调
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ close callbacks 如 socket.on(&amp;#39;close&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└───────────── timers ──────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一般规则：进入某 phase 后，先做该 phase 专属操作，再 FIFO 执行队列回调，直到队列耗尽或达到系统相关的上限，再进入下一 phase。&lt;/p&gt;
&lt;p&gt;文档还强调：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;timers&lt;/strong&gt; 指定的是“阈值（threshold）之后&lt;strong&gt;尽早&lt;/strong&gt;可执行”，不是精确闹钟；OS 调度与其它回调都会让它推迟。&lt;/li&gt;
&lt;li&gt;从 &lt;strong&gt;libuv 1.45.0（Node.js 20）&lt;/strong&gt; 起，&lt;strong&gt;每个事件循环迭代中 timers 在 poll 之后运行&lt;/strong&gt;；为兼容，进入循环前仍会跑一轮 timers。这会影响某些场景下 &lt;code&gt;setImmediate&lt;/code&gt; 与定时器的相对时序。&lt;/li&gt;
&lt;li&gt;每一轮事件循环之间，若不再等待任何异步 I/O 或定时器，进程可干净退出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-各-phase-在工程上的含义"&gt;&lt;a href="#2-%e5%90%84-phase-%e5%9c%a8%e5%b7%a5%e7%a8%8b%e4%b8%8a%e7%9a%84%e5%90%ab%e4%b9%89" class="header-anchor"&gt;&lt;/a&gt;2. 各 phase 在工程上的含义
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Phase&lt;/th&gt;
					&lt;th&gt;主要职责&lt;/th&gt;
					&lt;th&gt;工程注意&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;timers&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;setTimeout&lt;/code&gt; / &lt;code&gt;setInterval&lt;/code&gt; 到期回调&lt;/td&gt;
					&lt;td&gt;阈值语义；可能被长回调推迟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;pending callbacks&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;部分系统操作延迟回调（如某些 TCP &lt;code&gt;ECONNREFUSED&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;平台差异存在，但日常业务少直接依赖&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;poll&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;检索新 I/O；执行几乎所有 I/O 回调（除 close、定时器、&lt;code&gt;setImmediate&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;空队列时可能阻塞等待；有 &lt;code&gt;setImmediate&lt;/code&gt; 时可能结束 poll 进入 check&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;check&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;setImmediate&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;“poll 完成后立即跑一轮脚本”的专用相位&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;close callbacks&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;如 &lt;code&gt;socket.destroy()&lt;/code&gt; 触发的 &lt;code&gt;'close'&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;否则 close 也可能经 &lt;code&gt;nextTick&lt;/code&gt; 发出&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="3-processnexttick-不在相位图里"&gt;&lt;a href="#3-processnexttick-%e4%b8%8d%e5%9c%a8%e7%9b%b8%e4%bd%8d%e5%9b%be%e9%87%8c" class="header-anchor"&gt;&lt;/a&gt;3. &lt;code&gt;process.nextTick&lt;/code&gt; 不在相位图里
&lt;/h3&gt;&lt;p&gt;官方明确：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;process.nextTick()&lt;/code&gt; is &lt;strong&gt;not technically part of the event loop&lt;/strong&gt;. Instead, the &lt;strong&gt;nextTickQueue&lt;/strong&gt; will be processed after the current operation is completed, &lt;strong&gt;regardless of the current phase&lt;/strong&gt;.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;任意 phase 里调用 &lt;code&gt;nextTick&lt;/code&gt;，都会在&lt;strong&gt;当前操作结束后、事件循环继续前进前&lt;/strong&gt;排空 nextTick 队列。因此递归 &lt;code&gt;nextTick&lt;/code&gt; 可以 &lt;strong&gt;饿死 I/O&lt;/strong&gt;（永远到不了 poll）——这是合法但危险的行为。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;process&lt;/code&gt; API 进一步说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nextTick(callback)&lt;/code&gt; 把回调放进 &lt;strong&gt;next tick queue&lt;/strong&gt;；当前 JS 栈操作结束后、事件循环继续前&lt;strong&gt;完全排空&lt;/strong&gt;该队列。&lt;/li&gt;
&lt;li&gt;稳定性标注为 &lt;strong&gt;Legacy&lt;/strong&gt;，多数用户代码更推荐 &lt;strong&gt;&lt;code&gt;queueMicrotask()&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;每次 next tick 队列排空后，会&lt;strong&gt;立即&lt;/strong&gt;再排空 &lt;strong&gt;microtask 队列&lt;/strong&gt;（Promise 的 &lt;code&gt;then&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt;/&lt;code&gt;finally&lt;/code&gt;、&lt;code&gt;queueMicrotask&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模块系统差异&lt;/strong&gt;（官方示例）：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CJS&lt;/strong&gt;：通常 &lt;code&gt;nextTick&lt;/code&gt; → Promise/&lt;code&gt;queueMicrotask&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESM&lt;/strong&gt;：模块求值本身已在 microtask 路径上，常见顺序是 Promise/&lt;code&gt;queueMicrotask&lt;/code&gt; → &lt;code&gt;nextTick&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// CJS 常见输出：nextTick → resolve → microtask
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nextTick&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:process&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;resolve&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;queueMicrotask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;microtask&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextTick&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ESM 常见输出：resolve → microtask → nextTick
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nextTick&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;node:process&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;resolve&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;queueMicrotask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;microtask&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextTick&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-setimmediate-vs-settimeout0"&gt;&lt;a href="#4-setimmediate-vs-settimeout0" class="header-anchor"&gt;&lt;/a&gt;4. &lt;code&gt;setImmediate&lt;/code&gt; vs &lt;code&gt;setTimeout(0)&lt;/code&gt;
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;API&lt;/th&gt;
					&lt;th&gt;语义（官方）&lt;/th&gt;
					&lt;th&gt;典型落点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;setImmediate(fn)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;poll 阶段结束后执行脚本&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;check&lt;/strong&gt; phase&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;setTimeout(fn, 0)&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;至少等待阈值（0ms）后尽早执行&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;timers&lt;/strong&gt; phase&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主模块顶层&lt;/strong&gt;同时调度两者：顺序&lt;strong&gt;不确定&lt;/strong&gt;，取决于进程性能与调度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在 I/O 回调内&lt;/strong&gt;（例如 &lt;code&gt;fs.readFile&lt;/code&gt; 回调里）同时调度：官方保证 &lt;strong&gt;&lt;code&gt;setImmediate&lt;/code&gt; 总是先于 timers&lt;/strong&gt;，与 timers 数量无关。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方也坦言命名历史包袱：&lt;code&gt;nextTick&lt;/code&gt; 比 &lt;code&gt;setImmediate&lt;/code&gt; 更“立即”，名字几乎应互换；但为兼容 npm 生态不会改名。&lt;strong&gt;一般推荐优先用 &lt;code&gt;setImmediate&lt;/code&gt; 推理成本更低&lt;/strong&gt;；&lt;code&gt;nextTick&lt;/code&gt; 留给“必须在调用栈展开后、循环前进前”的场景。&lt;/p&gt;
&lt;h2 id="三实践可复现实验"&gt;&lt;a href="#%e4%b8%89%e5%ae%9e%e8%b7%b5%e5%8f%af%e5%a4%8d%e7%8e%b0%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;三、实践：可复现实验
&lt;/h2&gt;&lt;h3 id="实验-aio-周期内-immediate-恒先于-timeout0"&gt;&lt;a href="#%e5%ae%9e%e9%aa%8c-aio-%e5%91%a8%e6%9c%9f%e5%86%85-immediate-%e6%81%92%e5%85%88%e4%ba%8e-timeout0" class="header-anchor"&gt;&lt;/a&gt;实验 A：I/O 周期内 immediate 恒先于 timeout(0)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// save as io-order.js (CJS)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:fs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;timeout&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setImmediate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;immediate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node io-order.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 期望：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# immediate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# timeout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="实验-bnexttick-在当前操作后插入早于进入下一-phase"&gt;&lt;a href="#%e5%ae%9e%e9%aa%8c-bnexttick-%e5%9c%a8%e5%bd%93%e5%89%8d%e6%93%8d%e4%bd%9c%e5%90%8e%e6%8f%92%e5%85%a5%e6%97%a9%e4%ba%8e%e8%bf%9b%e5%85%a5%e4%b8%8b%e4%b8%80-phase" class="header-anchor"&gt;&lt;/a&gt;实验 B：nextTick 在“当前操作后”插入，早于进入下一 phase
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// nexttick-vs-immediate.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:fs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setImmediate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;immediate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nextTick&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;promise&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 I/O 回调这个“当前操作”末尾，会先排空 &lt;strong&gt;nextTick&lt;/strong&gt;，再处理 microtask，然后事件循环才能推进到后续 phase（check 中的 immediate）。你应看到 &lt;code&gt;nextTick&lt;/code&gt; 先于 &lt;code&gt;immediate&lt;/code&gt;；CJS 下 &lt;code&gt;nextTick&lt;/code&gt; 通常也先于 &lt;code&gt;promise&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="实验-c为何-api-必须全同步或全异步"&gt;&lt;a href="#%e5%ae%9e%e9%aa%8c-c%e4%b8%ba%e4%bd%95-api-%e5%bf%85%e9%a1%bb%e5%85%a8%e5%90%8c%e6%ad%a5%e6%88%96%e5%85%a8%e5%bc%82%e6%ad%a5" class="header-anchor"&gt;&lt;/a&gt;实验 C：为何 API 必须“全同步或全异步”
&lt;/h3&gt;&lt;p&gt;官方反例：签名像异步、实际同步调用回调，调用方无法判断后续语句与回调的先后：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// BAD：可能同步也可能异步
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;maybeSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:fs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;推荐：同步路径也 &lt;code&gt;nextTick(cb)&lt;/code&gt;（或统一走 Promise），保证“总是异步”：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nextTick&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:process&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:fs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;definitelyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;构造函数里 &lt;code&gt;emit&lt;/code&gt; 同理：应 &lt;code&gt;nextTick(() =&amp;gt; this.emit('event'))&lt;/code&gt;，让调用方先挂上监听器。&lt;/p&gt;
&lt;h3 id="实验-d同步计算堵死循环对照"&gt;&lt;a href="#%e5%ae%9e%e9%aa%8c-d%e5%90%8c%e6%ad%a5%e8%ae%a1%e7%ae%97%e5%a0%b5%e6%ad%bb%e5%be%aa%e7%8e%af%e5%af%b9%e7%85%a7" class="header-anchor"&gt;&lt;/a&gt;实验 D：同步计算堵死循环（对照）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// block-loop.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node:http&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/block&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// 同步忙等 3s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;blocked\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ok\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;listen 3456&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;开两个终端：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 终端 1：触发阻塞&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS http://127.0.0.1:3456/block &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 终端 2：立即请求轻量路径&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;time&lt;/span&gt; curl -sS http://127.0.0.1:3456/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会观察到轻量请求也被拖慢——因为 &lt;strong&gt;Event Loop 被同一线程的同步代码占满&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="四常见坑与排查"&gt;&lt;a href="#%e5%9b%9b%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;四、常见坑与排查
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;现象&lt;/th&gt;
					&lt;th&gt;可能机制&lt;/th&gt;
					&lt;th&gt;处理方向&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;全站偶发卡顿、延迟尖刺&lt;/td&gt;
					&lt;td&gt;同步 CPU 重活 / 大 JSON / 灾难正则 / 长循环&lt;/td&gt;
					&lt;td&gt;限制输入；分区 yield；&lt;code&gt;worker_threads&lt;/code&gt; / 子进程卸载&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;setTimeout(100)&lt;/code&gt; 变成 105ms+&lt;/td&gt;
					&lt;td&gt;poll 长回调占住队列；阈值语义 + 其它回调&lt;/td&gt;
					&lt;td&gt;缩短 poll 回调；避免在 I/O 回调里做重计算&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;顶层 &lt;code&gt;setTimeout(0)&lt;/code&gt; 与 &lt;code&gt;setImmediate&lt;/code&gt; 顺序随机&lt;/td&gt;
					&lt;td&gt;主模块非 I/O 周期，时序依赖性能&lt;/td&gt;
					&lt;td&gt;需要稳定顺序时放到 I/O 回调，或只用一种调度 API&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;递归 &lt;code&gt;nextTick&lt;/code&gt; 后 I/O 永不完成&lt;/td&gt;
					&lt;td&gt;nextTick 队列饿死 poll&lt;/td&gt;
					&lt;td&gt;改 &lt;code&gt;setImmediate&lt;/code&gt; / 批处理 + yield&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CJS/ESM 下 Promise 与 nextTick 顺序不一致&lt;/td&gt;
					&lt;td&gt;官方文档写明的队列次序差异&lt;/td&gt;
					&lt;td&gt;不要依赖跨模块类型的微次序；业务用明确的 async 编排&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;fs&lt;/code&gt;/&lt;code&gt;crypto&lt;/code&gt; 变慢但 CPU 不高&lt;/td&gt;
					&lt;td&gt;Worker Pool 打满&lt;/td&gt;
					&lt;td&gt;评估 &lt;code&gt;UV_THREADPOOL_SIZE&lt;/code&gt;、减少同步 &lt;code&gt;*Sync&lt;/code&gt;、合并小 I/O&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;同步 API 伪装异步&lt;/td&gt;
					&lt;td&gt;调用方竞态&lt;/td&gt;
					&lt;td&gt;统一异步边界；错误路径也 &lt;code&gt;nextTick&lt;/code&gt;/&lt;code&gt;queueMicrotask&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;不要阻塞 Event Loop 的检查清单（摘自官方思路）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个回调的计算复杂度是否随用户输入线性/超线性膨胀？是否有上限校验？&lt;/li&gt;
&lt;li&gt;是否存在嵌套量词正则（REDOS）？简单包含用 &lt;code&gt;indexOf&lt;/code&gt;/&lt;code&gt;includes&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;大对象是否在主线程 &lt;code&gt;JSON.parse&lt;/code&gt; / &lt;code&gt;stringify&lt;/code&gt;？能否限制体积或卸载到 Worker？&lt;/li&gt;
&lt;li&gt;是否误用 &lt;code&gt;*Sync&lt;/code&gt; 文件/加密 API 于请求路径？&lt;/li&gt;
&lt;li&gt;需要“下一轮再继续”时，默认优先 &lt;code&gt;setImmediate&lt;/code&gt; / &lt;code&gt;queueMicrotask&lt;/code&gt;，慎用无限 &lt;code&gt;nextTick&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;分区（Partitioning）示例思路&lt;/strong&gt;：把 O(n) 求和拆成多步，每步 O(1)，用 &lt;code&gt;setImmediate&lt;/code&gt;/&lt;code&gt;queueMicrotask&lt;/code&gt; 让出循环，避免单次回调霸占主线程。更重的任务则应 &lt;strong&gt;Offloading&lt;/strong&gt; 到 Worker（&lt;code&gt;worker_threads&lt;/code&gt;、独立进程或 C++/N-API 任务），而不是只靠在主循环里切片。&lt;/p&gt;
&lt;h2 id="五总结"&gt;&lt;a href="#%e4%ba%94%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;五、总结
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Node 的并发模型是 &lt;strong&gt;单线程 Event Loop + libuv Worker Pool&lt;/strong&gt;：I/O 与任务要“碎、短、可让出”。&lt;/li&gt;
&lt;li&gt;相位队列决定 &lt;strong&gt;timers / poll / check / close&lt;/strong&gt; 等回调归属；&lt;strong&gt;nextTick 与 microtask 是阶段之间的优先通道&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O 回调内&lt;/strong&gt; &lt;code&gt;setImmediate&lt;/code&gt; 稳定早于 &lt;code&gt;setTimeout(0)&lt;/code&gt;；主模块顶层两者顺序不稳定。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nextTick&lt;/code&gt; 适合“栈展开后、循环前进前”的 API 契约；滥用会饿死 I/O。新代码多数场景优先 &lt;strong&gt;&lt;code&gt;queueMicrotask&lt;/code&gt; / &lt;code&gt;setImmediate&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;性能与安全同一条线：限制输入、避免 REDOS、大 JSON 与 CPU 重活卸载，才能让“少线程高并发”真正成立。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把调度图画进团队约定后，再写中间件、ORM 钩子、连接池回调，会少掉一大类“玄学时序 bug”。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a class="link" href="https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick" target="_blank" rel="noopener"
 &gt;The Node.js Event Loop&lt;/a&gt; — Node.js Learn（阶段图、poll/check/timers、&lt;code&gt;setImmediate&lt;/code&gt; vs &lt;code&gt;setTimeout&lt;/code&gt;、&lt;code&gt;process.nextTick&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop" target="_blank" rel="noopener"
 &gt;Don&amp;rsquo;t Block the Event Loop (or the Worker Pool)&lt;/a&gt; — Node.js Learn（Event Loop vs Worker Pool、阻塞危害、分区/卸载、REDOS/JSON）&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nodejs.org/en/learn/asynchronous-work/understanding-processnexttick" target="_blank" rel="noopener"
 &gt;Understanding process.nextTick()&lt;/a&gt; — Node.js Learn&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nodejs.org/api/process.html#processnexttickcallback-args" target="_blank" rel="noopener"
 &gt;&lt;code&gt;process.nextTick&lt;/code&gt; / &lt;code&gt;queueMicrotask&lt;/code&gt; 对比&lt;/a&gt; — Node.js API（CJS/ESM 次序、Legacy 说明）&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nodejs.org/api/timers.html" target="_blank" rel="noopener"
 &gt;Timers&lt;/a&gt; — Node.js API（&lt;code&gt;setImmediate&lt;/code&gt; / &lt;code&gt;setTimeout&lt;/code&gt;、ref/unref）&lt;/li&gt;
&lt;li&gt;源码文档镜像：&lt;a class="link" href="https://raw.githubusercontent.com/nodejs/node/main/doc/api/process.md" target="_blank" rel="noopener"
 &gt;nodejs/node &lt;code&gt;doc/api/process.md&lt;/code&gt;&lt;/a&gt;、&lt;a class="link" href="https://raw.githubusercontent.com/nodejs/node/main/doc/api/timers.md" target="_blank" rel="noopener"
 &gt;&lt;code&gt;doc/api/timers.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Node.js 26.5.0 Current 发布：Web Streams、TLS 可观测性与权限模型修复实践指南</title><link>https://blog.waihost.com/posts/nodejs-26-5-current-upgrade-guide/</link><pubDate>Fri, 10 Jul 2026 09:02:00 +0800</pubDate><guid>https://blog.waihost.com/posts/nodejs-26-5-current-upgrade-guide/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/nodejs-26-5-current-upgrade-guide.svg" alt="Featured image of post Node.js 26.5.0 Current 发布：Web Streams、TLS 可观测性与权限模型修复实践指南" /&gt;&lt;p&gt;Node.js 26.5.0 已于 2026-07-08 发布，属于 Current 版本线的一次常规功能与维护更新。它不是安全发布，但包含了几类对后端服务、网关、边缘函数和内部平台都值得关注的变化：&lt;code&gt;Blob&lt;/code&gt; 新增面向流式读取的能力，TLS 协商信息更容易被观测，权限模型在 &lt;code&gt;NODE_OPTIONS&lt;/code&gt; 传播场景下修复了行为一致性问题，同时依赖组件继续更新到较新的版本。&lt;/p&gt;
&lt;p&gt;如果你的生产环境仍以 LTS 为主，不需要因为 Current 版本线的每次发布立即升级线上服务；但如果团队正在验证 Node.js 26、维护基础镜像、做运行时平台适配，26.5.0 很适合作为一次“小步升级 + 回归验证”的窗口。&lt;/p&gt;
&lt;h2 id="这次发布的定位"&gt;&lt;a href="#%e8%bf%99%e6%ac%a1%e5%8f%91%e5%b8%83%e7%9a%84%e5%ae%9a%e4%bd%8d" class="header-anchor"&gt;&lt;/a&gt;这次发布的定位
&lt;/h2&gt;&lt;p&gt;从官方 release index 可以看到，Node.js 26.5.0 的发布日期为 2026-07-08，随附组件版本包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;npm：&lt;code&gt;11.17.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;V8：&lt;code&gt;14.6.202.34&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;libuv：&lt;code&gt;1.52.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;zlib：&lt;code&gt;1.3.2.1-motley&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;OpenSSL：&lt;code&gt;3.5.7&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ABI modules：&lt;code&gt;147&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;security: false&lt;/code&gt;，即这不是一次安全专版发布&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着它更适合被理解为 Current 线的功能增强和维护版本，而不是“必须立刻全量升级”的安全补丁。不过，Current 线通常会提前暴露未来 LTS 可能影响应用的运行时变化，平台团队应该尽早把它纳入 CI 验证。&lt;/p&gt;
&lt;h2 id="关键变化一blobtextstream-让文本读取更贴近流式处理"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e5%8f%98%e5%8c%96%e4%b8%80blobtextstream-%e8%ae%a9%e6%96%87%e6%9c%ac%e8%af%bb%e5%8f%96%e6%9b%b4%e8%b4%b4%e8%bf%91%e6%b5%81%e5%bc%8f%e5%a4%84%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;关键变化一：&lt;code&gt;Blob.textStream()&lt;/code&gt; 让文本读取更贴近流式处理
&lt;/h2&gt;&lt;p&gt;官方发布说明将 &lt;code&gt;blob.textStream()&lt;/code&gt; 列为 notable change。过去我们处理 &lt;code&gt;Blob&lt;/code&gt; 文本内容时，常见方式是一次性调用 &lt;code&gt;blob.text()&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种写法简单，但它会把文本内容整体读入内存。对于普通接口请求体、配置片段或小文件来说问题不大；但在日志分析、对象存储网关、上传文件预处理等场景中，一次性读取会放大内存峰值，也不利于和 Web Streams 管线组合。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Blob.textStream()&lt;/code&gt; 的价值在于：它把“文本内容”暴露为可流式消费的接口，应用可以更自然地接入 &lt;code&gt;ReadableStream&lt;/code&gt;、&lt;code&gt;TextDecoderStream&lt;/code&gt;、分块处理、背压控制等模式。实践中建议关注三类用法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 伪代码：面向流式消费的处理方式
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 分块统计、过滤、写入下游，而不是一次性放进内存
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;processChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;升级验证时不要只跑“能否启动”的 smoke test，应补充大文件和高并发场景，观察 RSS、GC 暂停、吞吐量是否更稳定。&lt;/p&gt;
&lt;h2 id="关键变化二tls-协商组信息更容易排查"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e5%8f%98%e5%8c%96%e4%ba%8ctls-%e5%8d%8f%e5%95%86%e7%bb%84%e4%bf%a1%e6%81%af%e6%9b%b4%e5%ae%b9%e6%98%93%e6%8e%92%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;关键变化二：TLS 协商组信息更容易排查
&lt;/h2&gt;&lt;p&gt;26.5.0 还提到 “report negotiated TLS groups”。这类变化不一定会改变业务逻辑，但对运维和安全排障很有价值。&lt;/p&gt;
&lt;p&gt;在实际生产中，TLS 问题经常表现为“某些客户端能连、某些客户端失败”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端运行时、OpenSSL、系统证书库版本不同；&lt;/li&gt;
&lt;li&gt;负载均衡、反向代理、服务端 Node.js 的 TLS 配置不一致；&lt;/li&gt;
&lt;li&gt;安全基线升级后，曲线/密钥交换组被禁用或协商失败；&lt;/li&gt;
&lt;li&gt;FIPS、国密或企业代理环境引入额外限制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果运行时能报告协商到的 TLS groups，平台团队就能把“握手失败/降级/兼容性差”的问题从黑盒日志变成可观测事实。建议在升级验证阶段做一次完整 TLS 回归：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -p &lt;span class="s2"&gt;&amp;#34;process.versions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl s_client -connect example.com:443 -tls1_3 &amp;lt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时检查服务端访问日志、网关日志和 APM 标签，确认 TLS 版本、加密套件、协商组等信息是否能被统一记录。对于金融、企业内网、老旧 Android/WebView 客户端，这一步尤其重要。&lt;/p&gt;
&lt;h2 id="关键变化三权限模型与-node_options-的一致性修复"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e5%8f%98%e5%8c%96%e4%b8%89%e6%9d%83%e9%99%90%e6%a8%a1%e5%9e%8b%e4%b8%8e-node_options-%e7%9a%84%e4%b8%80%e8%87%b4%e6%80%a7%e4%bf%ae%e5%a4%8d" class="header-anchor"&gt;&lt;/a&gt;关键变化三：权限模型与 &lt;code&gt;NODE_OPTIONS&lt;/code&gt; 的一致性修复
&lt;/h2&gt;&lt;p&gt;Node.js 的 Permission Model 仍处于演进阶段，但已经是很多团队评估“最小权限运行 JavaScript 服务”的重要方向。26.5.0 的提交列表包含 “fix permission model propagation via NODE_OPTIONS”。&lt;/p&gt;
&lt;p&gt;这类修复值得平台团队认真看待，因为 &lt;code&gt;NODE_OPTIONS&lt;/code&gt; 在容器镜像、PaaS 平台、CI/CD、serverless 运行时里非常常见。例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--experimental-permission --allow-fs-read=/app/config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node server.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果权限相关选项在子进程、worker、工具链包装脚本中传播不一致，可能会出现两种风险：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开发/测试环境以为权限限制已生效，但生产包装脚本绕过了限制；&lt;/li&gt;
&lt;li&gt;本该允许的读取、网络或子进程行为被误拦截，导致线上难以复现的故障。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，升级到 26.5.0 后建议新增一组“权限模型回归用例”，覆盖：主进程、&lt;code&gt;child_process&lt;/code&gt;、worker、测试框架、构建脚本以及容器入口脚本。不要只验证应用入口文件。&lt;/p&gt;
&lt;h2 id="关键变化四依赖组件更新带来的隐性影响"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e5%8f%98%e5%8c%96%e5%9b%9b%e4%be%9d%e8%b5%96%e7%bb%84%e4%bb%b6%e6%9b%b4%e6%96%b0%e5%b8%a6%e6%9d%a5%e7%9a%84%e9%9a%90%e6%80%a7%e5%bd%b1%e5%93%8d" class="header-anchor"&gt;&lt;/a&gt;关键变化四：依赖组件更新带来的隐性影响
&lt;/h2&gt;&lt;p&gt;本次版本还包含若干依赖更新，例如 release notes 中列出的 Undici、nghttp3、SQLite 等更新；release index 也显示 OpenSSL、libuv、zlib 等底层组件处在较新的版本组合上。对大多数业务而言，这些更新不会直接要求改代码，但它们可能影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP 客户端行为：连接复用、代理、重定向、超时和 header 处理；&lt;/li&gt;
&lt;li&gt;HTTP/2 / HTTP/3 相关边界行为；&lt;/li&gt;
&lt;li&gt;内置 SQLite 场景的兼容性和性能；&lt;/li&gt;
&lt;li&gt;原生插件编译、ABI 兼容和镜像体积；&lt;/li&gt;
&lt;li&gt;TLS/加密能力与企业安全基线的匹配。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果项目依赖 &lt;code&gt;node-gyp&lt;/code&gt;、原生 addon、Electron、Playwright、Puppeteer 或自定义 OpenSSL 行为，建议在升级前后分别输出环境信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -p &lt;span class="s2"&gt;&amp;#34;process.version&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -p &lt;span class="s2"&gt;&amp;#34;process.versions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm ls --depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;并在 CI 中保留构建日志，方便定位 ABI 或编译链变化。&lt;/p&gt;
&lt;h2 id="推荐升级流程"&gt;&lt;a href="#%e6%8e%a8%e8%8d%90%e5%8d%87%e7%ba%a7%e6%b5%81%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;推荐升级流程
&lt;/h2&gt;&lt;p&gt;对于生产团队，建议把 Node.js 26.5.0 当作 Current 线验证版本，而不是直接替换 LTS：&lt;/p&gt;
&lt;h3 id="1-先确认版本线策略"&gt;&lt;a href="#1-%e5%85%88%e7%a1%ae%e8%ae%a4%e7%89%88%e6%9c%ac%e7%ba%bf%e7%ad%96%e7%95%a5" class="header-anchor"&gt;&lt;/a&gt;1. 先确认版本线策略
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;线上核心服务：优先使用当前 LTS；&lt;/li&gt;
&lt;li&gt;内部平台、SDK、CLI、基础镜像：可以提前验证 Current；&lt;/li&gt;
&lt;li&gt;新特性探索：用隔离环境验证，不直接影响生产流量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-建立最小回归清单"&gt;&lt;a href="#2-%e5%bb%ba%e7%ab%8b%e6%9c%80%e5%b0%8f%e5%9b%9e%e5%bd%92%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;2. 建立最小回归清单
&lt;/h3&gt;&lt;p&gt;至少覆盖以下命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -p &lt;span class="s2"&gt;&amp;#34;process.versions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm ci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Web 服务还应补充接口 smoke test、TLS 连接测试、代理场景测试和大文件上传/下载测试。&lt;/p&gt;
&lt;h3 id="3-对-web-streams-和-blob-场景做专项验证"&gt;&lt;a href="#3-%e5%af%b9-web-streams-%e5%92%8c-blob-%e5%9c%ba%e6%99%af%e5%81%9a%e4%b8%93%e9%a1%b9%e9%aa%8c%e8%af%81" class="header-anchor"&gt;&lt;/a&gt;3. 对 Web Streams 和 Blob 场景做专项验证
&lt;/h3&gt;&lt;p&gt;如果项目处理上传文件、对象存储、日志、CSV/JSONL 或边缘函数请求体，可以新增流式处理测试，避免把大对象一次性读入内存。&lt;/p&gt;
&lt;h3 id="4-对权限模型做入口链路测试"&gt;&lt;a href="#4-%e5%af%b9%e6%9d%83%e9%99%90%e6%a8%a1%e5%9e%8b%e5%81%9a%e5%85%a5%e5%8f%a3%e9%93%be%e8%b7%af%e6%b5%8b%e8%af%95" class="header-anchor"&gt;&lt;/a&gt;4. 对权限模型做“入口链路”测试
&lt;/h3&gt;&lt;p&gt;如果你正在使用或评估 &lt;code&gt;--experimental-permission&lt;/code&gt;，不要只测 &lt;code&gt;node app.js&lt;/code&gt;。要覆盖 npm script、PM2/systemd、Docker ENTRYPOINT、测试框架和子进程。&lt;/p&gt;
&lt;h3 id="5-保留快速回滚方案"&gt;&lt;a href="#5-%e4%bf%9d%e7%95%99%e5%bf%ab%e9%80%9f%e5%9b%9e%e6%bb%9a%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;5. 保留快速回滚方案
&lt;/h3&gt;&lt;p&gt;升级运行时比升级普通依赖更底层。建议镜像 tag 明确区分：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node:26.4.0-bookworm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node:26.5.0-bookworm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在灰度阶段保留旧镜像和回滚脚本，避免运行时行为变化影响全量流量。&lt;/p&gt;
&lt;h2 id="注意事项"&gt;&lt;a href="#%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;注意事项
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;26.5.0 是 Current，不等同于 LTS；生产环境是否采用要看团队的运行时策略。&lt;/li&gt;
&lt;li&gt;这不是安全发布，不应把它包装成“紧急安全升级”。&lt;/li&gt;
&lt;li&gt;权限模型仍要关注实验性接口和未来变更，不建议仅凭一次修复就假设所有沙箱场景都已稳定。&lt;/li&gt;
&lt;li&gt;原生 addon 和构建链要在目标架构上验证，尤其是 Alpine、ARM64、CI runner 与生产镜像不一致时。&lt;/li&gt;
&lt;li&gt;如果线上仍在 Node.js 22/24 LTS，优先处理对应 LTS 安全更新，再安排 Node.js 26 的兼容性验证。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;Node.js 26.5.0 的重点不在“惊天动地的新功能”，而在几个对工程实践很实用的方向：更流式的 &lt;code&gt;Blob&lt;/code&gt; 文本读取、更好的 TLS 可观测性、权限模型传播修复，以及底层依赖维护更新。对平台团队来说，最合理的姿势是：把它纳入 CI 和预发环境，围绕 Web Streams、TLS、权限模型、原生 addon 建立回归清单；对业务团队来说，则应继续遵循 LTS 优先策略，等验证充分后再决定是否升级运行时基线。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;官方发布说明：&lt;a class="link" href="https://nodejs.org/en/blog/release/v26.5.0" target="_blank" rel="noopener"
 &gt;https://nodejs.org/en/blog/release/v26.5.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;官方 release index：&lt;a class="link" href="https://nodejs.org/download/release/index.json" target="_blank" rel="noopener"
 &gt;https://nodejs.org/download/release/index.json&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub Release：&lt;a class="link" href="https://github.com/nodejs/node/releases/tag/v26.5.0" target="_blank" rel="noopener"
 &gt;https://github.com/nodejs/node/releases/tag/v26.5.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Node.js Permission Model 文档：&lt;a class="link" href="https://nodejs.org/api/permissions.html" target="_blank" rel="noopener"
 &gt;https://nodejs.org/api/permissions.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Node.js Web Streams 文档：&lt;a class="link" href="https://nodejs.org/api/webstreams.html" target="_blank" rel="noopener"
 &gt;https://nodejs.org/api/webstreams.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Node.js 2026 年 6 月安全发布：22/24/26 版本线升级与排查指南</title><link>https://blog.waihost.com/posts/nodejs-june-2026-security-release-guide/</link><pubDate>Thu, 02 Jul 2026 01:16:08 +0000</pubDate><guid>https://blog.waihost.com/posts/nodejs-june-2026-security-release-guide/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/nodejs-june-2026-security-release-guide.svg" alt="Featured image of post Node.js 2026 年 6 月安全发布：22/24/26 版本线升级与排查指南" /&gt;&lt;p&gt;Node.js 项目在 2026 年 6 月 18 日发布了一组安全更新，覆盖 &lt;code&gt;26.x&lt;/code&gt;、&lt;code&gt;24.x&lt;/code&gt;、&lt;code&gt;22.x&lt;/code&gt; 三条仍受支持的发布线。对线上服务来说，这不是一次“可有可无”的例行小版本：官方公告中最高风险等级为 &lt;strong&gt;High&lt;/strong&gt;，并同时修复了 WebCrypto、TLS 主机名校验、HTTP/2、代理错误信息、Permission Model 等多个方向的问题。&lt;/p&gt;
&lt;p&gt;如果你的生产环境仍在使用 Node.js 22、24 或 26，建议把这次更新当作一次安全基线升级来处理：先识别运行版本，再按发布线升级到对应修复版本，最后用针对性的回归测试覆盖 TLS、HTTP/2、代理、权限模型和大数据加密场景。&lt;/p&gt;
&lt;h2 id="先看结论应该升级到哪些版本"&gt;&lt;a href="#%e5%85%88%e7%9c%8b%e7%bb%93%e8%ae%ba%e5%ba%94%e8%af%a5%e5%8d%87%e7%ba%a7%e5%88%b0%e5%93%aa%e4%ba%9b%e7%89%88%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;先看结论：应该升级到哪些版本
&lt;/h2&gt;&lt;p&gt;这次安全发布对应的修复版本如下：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;发布线&lt;/th&gt;
					&lt;th style="text-align: right"&gt;修复版本&lt;/th&gt;
					&lt;th&gt;版本状态&lt;/th&gt;
					&lt;th&gt;备注&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Node.js 22&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;code&gt;v22.23.0&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;LTS，代号 &lt;code&gt;Jod&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;安全发布，适合仍在 22 LTS 的生产系统&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Node.js 24&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;code&gt;v24.17.0&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;LTS，代号 &lt;code&gt;Krypton&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;安全发布，适合 24 LTS 生产系统&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Node.js 26&lt;/td&gt;
					&lt;td style="text-align: right"&gt;&lt;code&gt;v26.3.1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Current&lt;/td&gt;
					&lt;td&gt;安全发布，适合已经采用 26 Current 的系统&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;官方发布索引显示，这三个版本的发布日期均为 &lt;code&gt;2026-06-17&lt;/code&gt;，并带有 security 标记；官方博客随后在 &lt;code&gt;2026-06-18&lt;/code&gt; 更新公告，说明安全版本已经可用。&lt;/p&gt;
&lt;p&gt;另外，Node.js 发布计划显示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v22&lt;/code&gt; 的维护期到 &lt;code&gt;2027-04-30&lt;/code&gt;，LTS 代号为 &lt;code&gt;Jod&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v24&lt;/code&gt; 的维护期到 &lt;code&gt;2028-04-30&lt;/code&gt;，LTS 代号为 &lt;code&gt;Krypton&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v26&lt;/code&gt; 在 &lt;code&gt;2026-05-05&lt;/code&gt; 开始，计划于 &lt;code&gt;2026-10-28&lt;/code&gt; 进入 LTS，当前仍是 Current 线。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，保守生产系统通常应优先停留在当前 LTS 线并升级补丁版本；已经使用 Current 的团队，则应尽快吸收 &lt;code&gt;v26.3.1&lt;/code&gt; 或后续包含该修复的更高版本。&lt;/p&gt;
&lt;h2 id="这次修复覆盖了哪些风险"&gt;&lt;a href="#%e8%bf%99%e6%ac%a1%e4%bf%ae%e5%a4%8d%e8%a6%86%e7%9b%96%e4%ba%86%e5%93%aa%e4%ba%9b%e9%a3%8e%e9%99%a9" class="header-anchor"&gt;&lt;/a&gt;这次修复覆盖了哪些风险
&lt;/h2&gt;&lt;p&gt;官方公告列出的修复点较多，可以按工程影响分成几类理解。&lt;/p&gt;
&lt;h3 id="1-webcrypto超大输入可能导致进程中止"&gt;&lt;a href="#1-webcrypto%e8%b6%85%e5%a4%a7%e8%be%93%e5%85%a5%e5%8f%af%e8%83%bd%e5%af%bc%e8%87%b4%e8%bf%9b%e7%a8%8b%e4%b8%ad%e6%ad%a2" class="header-anchor"&gt;&lt;/a&gt;1. WebCrypto：超大输入可能导致进程中止
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;CVE-2026-48933&lt;/code&gt; 影响 Node.js WebCrypto 的 AES 加密实现。当 &lt;code&gt;subtle.encrypt()&lt;/code&gt; 的输入大小是 &lt;code&gt;2GiB&lt;/code&gt; 的倍数时，可能触发整数溢出并导致进程崩溃，官方定级为 High。&lt;/p&gt;
&lt;p&gt;这类问题对常规 API 服务未必高频触发，但对以下场景值得重点检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务允许用户上传或处理超大二进制文件；&lt;/li&gt;
&lt;li&gt;使用 WebCrypto 做文件级加密、备份加密或对象存储前置加密；&lt;/li&gt;
&lt;li&gt;加密任务运行在常驻 Node.js worker 中，进程崩溃会影响队列消费或批处理任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;升级之外，还建议在业务层对单次加密输入设置合理上限，避免把“超大对象完整读入内存后一次性加密”作为默认实现。&lt;/p&gt;
&lt;h3 id="2-tls--mtls主机名规范化与会话复用相关绕过"&gt;&lt;a href="#2-tls--mtls%e4%b8%bb%e6%9c%ba%e5%90%8d%e8%a7%84%e8%8c%83%e5%8c%96%e4%b8%8e%e4%bc%9a%e8%af%9d%e5%a4%8d%e7%94%a8%e7%9b%b8%e5%85%b3%e7%bb%95%e8%bf%87" class="header-anchor"&gt;&lt;/a&gt;2. TLS / mTLS：主机名规范化与会话复用相关绕过
&lt;/h3&gt;&lt;p&gt;本次公告中多项漏洞与 TLS 主机名处理有关，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48618&lt;/code&gt;：Unicode 点分隔符处理与解析器、校验器之间的主机名规范化不一致，可能导致通配符深度认证绕过；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48928&lt;/code&gt;：SNI 上下文匹配存在大小写敏感问题，在多上下文 mTLS 场景下可能绕过授权策略；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48930&lt;/code&gt;：嵌入 NUL 字符的主机名可能因为 C 字符串截断造成 authority rebinding；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48934&lt;/code&gt;：不同 &lt;code&gt;servername&lt;/code&gt; 的 TLS 会话复用可能导致主机身份校验绕过。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题对普通内部脚本也许不明显，但对网关、反向代理、服务网格边车、BFF、Webhook 分发器、多租户 mTLS 接入层影响更大。升级后建议重点回归：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用通配符证书的 HTTPS 客户端连接；&lt;/li&gt;
&lt;li&gt;同一进程内面向多个上游域名的 keep-alive / TLS session 复用；&lt;/li&gt;
&lt;li&gt;mTLS 中按 SNI 或证书主机名选择租户、策略、证书链的逻辑；&lt;/li&gt;
&lt;li&gt;对国际化域名、大小写混合域名、异常主机名输入的拒绝策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果业务代码里手写了 &lt;code&gt;checkServerIdentity&lt;/code&gt; 或自定义 &lt;code&gt;tls.connect()&lt;/code&gt; 参数，也应把这些路径纳入审计。&lt;/p&gt;
&lt;h3 id="3-http2客户端内存增长与服务端-goaway-清理问题"&gt;&lt;a href="#3-http2%e5%ae%a2%e6%88%b7%e7%ab%af%e5%86%85%e5%ad%98%e5%a2%9e%e9%95%bf%e4%b8%8e%e6%9c%8d%e5%8a%a1%e7%ab%af-goaway-%e6%b8%85%e7%90%86%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;3. HTTP/2：客户端内存增长与服务端 GOAWAY 清理问题
&lt;/h3&gt;&lt;p&gt;公告列出两个 HTTP/2 相关问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48619&lt;/code&gt;：HTTP/2 客户端可能因为攻击者控制的 &lt;code&gt;ORIGIN&lt;/code&gt; frame 出现无界内存增长，最终导致 OOM；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48937&lt;/code&gt;：HTTP/2 服务端在无效协议错误后发送 &lt;code&gt;GOAWAY&lt;/code&gt;，但 session 未正确清理，仍可能继续接收数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你的 Node.js 服务直接使用 &lt;code&gt;node:http2&lt;/code&gt;，或运行在会与不可信上游/下游建立 HTTP/2 连接的代理组件中，升级后建议做两类验证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;压测长连接场景，观察连接关闭、错误、重连后内存是否稳定；&lt;/li&gt;
&lt;li&gt;在网关、服务端渲染、爬虫、聚合 API 等客户端角色中，确认异常 HTTP/2 响应不会让 worker 内存持续增长。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;即使框架层封装了 HTTP/2，也不要只测 HTTP/1.1 路径。&lt;/p&gt;
&lt;h3 id="4-代理与日志凭据可能进入错误信息"&gt;&lt;a href="#4-%e4%bb%a3%e7%90%86%e4%b8%8e%e6%97%a5%e5%bf%97%e5%87%ad%e6%8d%ae%e5%8f%af%e8%83%bd%e8%bf%9b%e5%85%a5%e9%94%99%e8%af%af%e4%bf%a1%e6%81%af" class="header-anchor"&gt;&lt;/a&gt;4. 代理与日志：凭据可能进入错误信息
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;CVE-2026-48615&lt;/code&gt; 与 &lt;code&gt;ERR_PROXY_TUNNEL&lt;/code&gt; 错误处理有关。当代理凭据嵌入代理 URL 时，错误信息中可能泄露这些凭据，并被日志、诊断系统或错误上报平台采集。&lt;/p&gt;
&lt;p&gt;升级运行时后，还建议额外做一次配置治理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要在日志中直接打印完整代理 URL；&lt;/li&gt;
&lt;li&gt;对 &lt;code&gt;HTTP_PROXY&lt;/code&gt;、&lt;code&gt;HTTPS_PROXY&lt;/code&gt;、&lt;code&gt;NO_PROXY&lt;/code&gt;、自定义代理配置做脱敏；&lt;/li&gt;
&lt;li&gt;在日志平台中搜索历史 &lt;code&gt;ERR_PROXY_TUNNEL&lt;/code&gt;、&lt;code&gt;proxy&lt;/code&gt;、&lt;code&gt;@&lt;/code&gt; 等关键词，评估是否已有敏感信息进入日志；&lt;/li&gt;
&lt;li&gt;优先使用密钥管理系统或运行时注入，而不是把代理账号密码写入仓库配置文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这类漏洞的直接修复在运行时，但真正的风险收敛通常还包括日志脱敏和凭据轮换。&lt;/p&gt;
&lt;h3 id="5-permission-model仍需谨慎对待实验性隔离能力"&gt;&lt;a href="#5-permission-model%e4%bb%8d%e9%9c%80%e8%b0%a8%e6%85%8e%e5%af%b9%e5%be%85%e5%ae%9e%e9%aa%8c%e6%80%a7%e9%9a%94%e7%a6%bb%e8%83%bd%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;5. Permission Model：仍需谨慎对待实验性隔离能力
&lt;/h3&gt;&lt;p&gt;本次安全发布还修复了多个 Permission Model 相关问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48617&lt;/code&gt;：&lt;code&gt;process.report.writeReport()&lt;/code&gt; 路径校验问题可能绕过权限模型；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48935&lt;/code&gt;：&lt;code&gt;FileHandle.utimes()&lt;/code&gt; 在 promises API 中可能修改只读路径的文件元数据；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CVE-2026-48936&lt;/code&gt;：Unix domain socket server 可绕过 &lt;code&gt;--permission&lt;/code&gt; 网络限制，这是 &lt;code&gt;CVE-2026-21636&lt;/code&gt; 修复不完整后的补充问题，并且官方说明只影响 Node.js 26。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你已经在容器、插件系统、在线代码执行、CI worker 中尝试 Node.js Permission Model，要注意两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;运行时权限模型不能替代容器、seccomp、AppArmor、只读文件系统、网络策略等外层隔离；&lt;/li&gt;
&lt;li&gt;升级后要重新跑“禁止写文件、禁止开网络、禁止生成报告”等负向测试，而不是只看正常用例是否通过。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Permission Model 能降低误用风险，但不应被设计成唯一安全边界。&lt;/p&gt;
&lt;h2 id="升级前的资产盘点"&gt;&lt;a href="#%e5%8d%87%e7%ba%a7%e5%89%8d%e7%9a%84%e8%b5%84%e4%ba%a7%e7%9b%98%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;升级前的资产盘点
&lt;/h2&gt;&lt;p&gt;建议先在服务器、容器镜像、CI runner、本地开发模板中统一盘点 Node.js 版本。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果使用 Docker，可以检查基础镜像标签：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker images &lt;span class="p"&gt;|&lt;/span&gt; grep node
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果服务由 systemd 管理，可以确认实际执行路径：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl cat your-node-service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;which node
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果使用 nvm、fnm、Volta、asdf 等版本管理器，尤其要注意“交互式 shell 里的版本”和“服务启动时的版本”可能不一致。最终判断标准应是生产进程实际使用的 &lt;code&gt;node&lt;/code&gt; 可执行文件。&lt;/p&gt;
&lt;h2 id="推荐升级路径"&gt;&lt;a href="#%e6%8e%a8%e8%8d%90%e5%8d%87%e7%ba%a7%e8%b7%af%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;推荐升级路径
&lt;/h2&gt;&lt;h3 id="使用官方二进制或系统包"&gt;&lt;a href="#%e4%bd%bf%e7%94%a8%e5%ae%98%e6%96%b9%e4%ba%8c%e8%bf%9b%e5%88%b6%e6%88%96%e7%b3%bb%e7%bb%9f%e5%8c%85" class="header-anchor"&gt;&lt;/a&gt;使用官方二进制或系统包
&lt;/h3&gt;&lt;p&gt;如果你的部署方式是官方二进制、NodeSource、发行版包或内部镜像，原则是升级到同一发布线的修复版本或后续补丁版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 示例：确认升级后版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 期望看到 v22.23.0 / v24.17.0 / v26.3.1 或更高且包含修复的版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;生产环境不建议在同一次安全修复中顺手跨大版本，例如从 22 LTS 直接跳到 26 Current。更稳妥的做法是：先在当前发布线打上安全补丁，再单独规划大版本迁移。&lt;/p&gt;
&lt;h3 id="使用-docker-镜像"&gt;&lt;a href="#%e4%bd%bf%e7%94%a8-docker-%e9%95%9c%e5%83%8f" class="header-anchor"&gt;&lt;/a&gt;使用 Docker 镜像
&lt;/h3&gt;&lt;p&gt;如果服务基于 &lt;code&gt;node&lt;/code&gt; 官方镜像，建议明确固定到补丁版本或内部审核过的基础镜像，而不是长期使用浮动标签：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:24.17.0-bookworm-slim&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; package*.json ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; npm ci --omit&lt;span class="o"&gt;=&lt;/span&gt;dev&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; . .&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;server.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;镜像升级后要重新构建并触发依赖扫描：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build -t your-app:node-24-17-0 .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run --rm your-app:node-24-17-0 node -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果组织内部有统一基础镜像，应优先让平台团队发布修复后的 base image，再由业务服务滚动升级，避免每个仓库各自处理。&lt;/p&gt;
&lt;h3 id="使用-nvm--fnm--volta"&gt;&lt;a href="#%e4%bd%bf%e7%94%a8-nvm--fnm--volta" class="header-anchor"&gt;&lt;/a&gt;使用 nvm / fnm / Volta
&lt;/h3&gt;&lt;p&gt;开发与 CI 环境可以同步更新版本声明文件，例如 &lt;code&gt;.nvmrc&lt;/code&gt;、&lt;code&gt;.node-version&lt;/code&gt; 或 Volta 配置。示例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .nvmrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;24.17.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后在 CI 中增加版本输出，避免流水线实际使用旧版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm ci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="回归测试清单"&gt;&lt;a href="#%e5%9b%9e%e5%bd%92%e6%b5%8b%e8%af%95%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;回归测试清单
&lt;/h2&gt;&lt;p&gt;这次升级后，建议至少覆盖下面几类测试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;启动与依赖安装&lt;/strong&gt;：&lt;code&gt;npm ci&lt;/code&gt;、应用启动、健康检查；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS 出站请求&lt;/strong&gt;：访问多个 HTTPS 上游，覆盖 keep-alive、代理、证书校验失败路径；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mTLS / 多租户接入&lt;/strong&gt;：验证大小写域名、通配符证书、SNI 路由与租户隔离；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2&lt;/strong&gt;：如果使用 &lt;code&gt;node:http2&lt;/code&gt; 或框架启用了 HTTP/2，测试长连接、异常断开、内存稳定性；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebCrypto&lt;/strong&gt;：覆盖加密、解密、异常输入、超大输入保护；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permission Model&lt;/strong&gt;：如果启用 &lt;code&gt;--permission&lt;/code&gt;，同时跑允许与拒绝场景；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志脱敏&lt;/strong&gt;：确认代理 URL、凭据、TLS 错误不会原样进入日志平台。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对高流量服务，建议先在灰度环境观察 RSS、heap、连接数、错误率、HTTP 5xx、TLS 握手失败率，再逐步扩大流量。&lt;/p&gt;
&lt;h2 id="线上排查如何确认已经修复"&gt;&lt;a href="#%e7%ba%bf%e4%b8%8a%e6%8e%92%e6%9f%a5%e5%a6%82%e4%bd%95%e7%a1%ae%e8%ae%a4%e5%b7%b2%e7%bb%8f%e4%bf%ae%e5%a4%8d" class="header-anchor"&gt;&lt;/a&gt;线上排查：如何确认已经修复
&lt;/h2&gt;&lt;p&gt;升级完成后，可以从三个层面确认：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;进程版本&lt;/strong&gt;：在容器或服务器内执行 &lt;code&gt;node -v&lt;/code&gt;，确认不是只更新了镜像标签；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;镜像版本&lt;/strong&gt;：检查部署平台实际拉取的镜像 digest，避免 Kubernetes 或 PaaS 仍运行旧副本；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志与指标&lt;/strong&gt;：观察升级后是否仍出现代理凭据泄露、HTTP/2 OOM、TLS 主机名校验异常等问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kubernetes 环境中可以用类似方式确认正在运行的镜像：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pods -l &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-app -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; deploy/your-app -- node -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果发现部分副本仍是旧版本，优先检查滚动更新策略、镜像拉取策略、节点缓存与多架构镜像是否同步。&lt;/p&gt;
&lt;h2 id="常见误区"&gt;&lt;a href="#%e5%b8%b8%e8%a7%81%e8%af%af%e5%8c%ba" class="header-anchor"&gt;&lt;/a&gt;常见误区
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;误区一：只升级开发机，不升级生产镜像。&lt;/strong&gt; 这类安全发布的目标是生产运行时，开发机版本一致只是辅助。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;误区二：看到后续版本就直接跨大版本。&lt;/strong&gt; 如果系统当前在 22 LTS，通常先升级到 22 线的安全补丁更可控；跨到 24 或 26 应另做兼容性评估。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;误区三：只看框架版本，不看 Node.js 运行时。&lt;/strong&gt; Express、NestJS、Next.js 等框架无法替代运行时漏洞修复。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;误区四：把 Permission Model 当成唯一沙箱。&lt;/strong&gt; 运行时权限限制应与容器、系统权限、网络隔离叠加使用。&lt;/p&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;这次 Node.js 2026 年 6 月安全发布覆盖面广，重点不只是“升级一个小版本”，还涉及 TLS/mTLS、HTTP/2、WebCrypto、代理日志、权限模型等多个运行时边界。推荐的处理顺序是：先盘点所有 Node.js 运行时，再升级到 &lt;code&gt;v22.23.0&lt;/code&gt;、&lt;code&gt;v24.17.0&lt;/code&gt;、&lt;code&gt;v26.3.1&lt;/code&gt; 或包含修复的后续版本，最后用针对性回归测试确认关键路径稳定。&lt;/p&gt;
&lt;p&gt;对生产系统而言，安全补丁最怕“以为已经升级”。真正完成升级的标志不是配置文件改了，而是线上进程、容器镜像、CI 输出、监控指标都能证明新版本已经生效。&lt;/p&gt;</description></item></channel></rss>