我在 JavaScript 里用 setTimeout 0 想让一段逻辑稍后立即执行来排个时序,结果它总是排在所有 Promise 回调的后面执行,输出顺序跟我写的顺序完全对不上,因为微任务永远先于宏任务的深度复盘

我有段逻辑想让它在当前同步代码跑完后尽快执行,用了 setTimeout(fn, 0),以为 0 毫秒就是马上、最先执行。可代码里同时还有 Promise.then 回调,运行结果让我懵了:setTimeout(0) 总是排在所有 Promise.then 回调的后面执行,日志顺序和书写顺序完全对不上,靠它排的时序也错乱。复盘才搞懂:JS 单线程靠事件循环调度异步,异步分宏任务(setTimeout/IO/事件)和微任务(Promise.then/await 后续/queueMicrotask);事件循环的规则是执行完同步代码→清空所有微任务→取一个宏任务→再清空所有微任务……微任务永远先于宏任务,所以 Promise.then 总先于 setTimeout(0);而 setTimeout(0) 的 0 也不是立即,它是宏任务、还有最小延迟。我误把异步想象成按书写顺序或延迟执行,想靠 setTimeout(0) 排时序自然全乱。这篇复盘从故障现场讲到事件循环与宏微任务、执行规则、setTimeout(0) 非立即,再到等异步用 await/then、等渲染用 requestAnimationFrame、并发用 Promise.all、让出主线程按需选微宏任务的完整正解,以及其他误判执行顺序的坑,和实际顺序由底层调度规则决定而非表面书写顺序、凭直觉推断异步时序必错、要理解规则并用明确机制表达时序依赖的认知。

我在 JavaScript 里用 setTimeout 0 想让一段逻辑"稍后立即"执行来排个时序,结果它总是排在所有 Promise 回调的后面执行,输出顺序跟我写的顺序完全对不上,因为微任务永远先于宏任务:一次没搞懂事件循环、误以为异步按书写顺序执行的深度复盘

那个"日志顺序乱成一团、靠 setTimeout 排的时序完全不灵"的 bug,逼我把 JavaScript 的事件循环彻底搞明白了。我有段逻辑,想让它"在当前这一波同步代码跑完之后、尽快执行",于是用了 setTimeout(fn, 0)——我以为"0 毫秒嘛,就是'马上、最先'执行"。可代码里同时还有一些 Promise.then(...) 回调。运行结果让我懵了:我以为会"先执行的 setTimeout(0)",结果总是排在所有 Promise.then 回调的后面执行;日志输出的顺序,和我代码里书写的顺序完全对不上;我用 setTimeout(0) 想"等某个状态更新完再执行"的逻辑,也时序错乱、不可靠。复盘 JS 的事件循环,我才彻底搞懂,后背发凉:问题出在我以为"异步任务按我写的顺序、或按延迟时间执行",却不知道 JS 有"宏任务 / 微任务"两个不同优先级的队列,执行顺序由事件循环的调度规则决定。JavaScript 是单线程的,靠"事件循环(event loop)"调度异步任务;而异步任务分两类:宏任务(macrotask:setTimeout/setInterval/IO/UI 事件等)和微任务(microtask:Promise.then/queueMicrotask/await 后续等);事件循环的规则是:执行完当前同步代码 → 清空所有微任务队列 → 再取一个宏任务执行 → 又清空所有微任务 → ……;关键在于:每一轮里,微任务永远先于宏任务执行,且会一次性清空;所以我的 Promise.then(微任务)总是先于 setTimeout(0)(宏任务)跑——哪怕 setTimeout 延迟是 0;而且 setTimeout(fn, 0) 的"0"也不是"立即":它只是"尽快加入宏任务队列",实际还要等当前同步代码 + 所有微任务跑完、且有最小延迟(约 4ms);误把"异步"想象成"按书写顺序或延迟时间执行",完全没意识到背后有一套明确的、和书写顺序无关的调度规则,于是想靠 setTimeout(0) "排时序",自然全乱。根本原因是:JS 事件循环把异步任务分宏任务和微任务,每轮先清空所有微任务再取一个宏任务,微任务永远先于宏任务;setTimeout(0) 是宏任务、不是立即执行;我误以为异步按书写顺序或延迟执行、想用 setTimeout(0) 排时序,与真实调度规则相悖导致顺序错乱。问题的根,是没搞懂事件循环——异步执行顺序由宏/微任务调度规则决定(微任务先于宏任务)、而非书写顺序或延迟;用 setTimeout(0) 投机排时序不可靠。这篇就把这次"事件循环"的坑,从头到尾复盘一遍。

故障现场:setTimeout(0) 总在 Promise 之后

问题在于误以为 setTimeout(0) 会"立即/最先"执行:

console.log("1 同步");

setTimeout(() => console.log("4 宏任务 setTimeout(0)"), 0);   // 我以为它会很早执行

Promise.resolve().then(() => console.log("3 微任务 Promise.then"));

console.log("2 同步");

// 我以为的顺序(按书写/延迟): 1, 4(0毫秒最快), 3, 2 ...
// 实际输出顺序:
//   1 同步
//   2 同步                    ← 同步代码先全部执行
//   3 微任务 Promise.then     ← 同步后, 清空所有微任务
//   4 宏任务 setTimeout(0)    ← 最后才取宏任务执行!即使延迟是0

/*
为什么是这个顺序(事件循环的规则):
  1. 先执行完当前所有【同步代码】(1、2);
  2. 然后【清空所有微任务队列】(Promise.then、queueMicrotask、await后续) → 3;
  3. 再从【宏任务队列】取【一个】执行(setTimeout、setInterval、IO、事件) → 4;
  4. 执行完这个宏任务后, 又【清空所有微任务】, 再取下一个宏任务... 如此循环。

  关键结论:
  - 微任务【永远先于】宏任务(同一轮里, 微任务清空了才轮到宏任务);
  - setTimeout(fn, 0) 的 0 不是"立即": 它是宏任务, 要等同步代码 + 所有微任务跑完;
    且浏览器有最小延迟(嵌套超过一定层数约4ms), 0只是"尽快入宏任务队列";
  - 异步任务的执行顺序, 由【事件循环的调度规则 + 任务类型】决定, 不是按你写的顺序!

  常见的宏任务: setTimeout、setInterval、setImmediate(Node)、I/O、UI渲染、消息事件;
  常见的微任务: Promise.then/catch/finally、await之后的代码、queueMicrotask、MutationObserver。

★ 核心: JS单线程靠事件循环调度异步; 异步分宏任务/微任务, 每轮先清空所有微任务再取一个宏任务,
  微任务永远先于宏任务; setTimeout(0)是宏任务不是立即执行; 别用书写顺序/setTimeout(0)去想当然排时序。
*/

看着实际输出"1、2、3、4"和我以为的顺序对不上,尤其 setTimeout(0) 排在最后,我又困惑又恍然:"我一直以为异步就是'谁延迟短谁先跑、或按我写的顺序跑'……谁知道背后有宏任务、微任务两个队列,微任务永远插在宏任务前面!我用 setTimeout(0) 想'抢先排个序',结果它反而是最后执行的。"这个坑最隐蔽的地方在于:在简单场景下"碰巧"也能跑对(只有同步或只有一类异步时,顺序符合直觉);它只在"宏任务和微任务混在一起"时才暴露,而异步代码一复杂,这种混合就无处不在;而且这种"顺序错乱"往往不报错,只是行为诡异、结果时对时错,极难定位下面就来拆解,事件循环和异步时序到底该怎么处理。

第一件事:搞懂事件循环与宏/微任务

我顺着这次事故,把 JS 的事件循环和异步调度彻底理清了。

JS 异步到底按什么顺序执行? 事件循环怎么工作?

【核心: JS单线程靠事件循环调度; 异步分宏任务(setTimeout/IO/事件)和微任务(Promise.then/await/queueMicrotask);
   每轮: 跑完同步→清空所有微任务→取一个宏任务→再清空所有微任务...; 微任务永远先于宏任务; setTimeout(0)非立即】

1. 单线程 + 事件循环:
   - JS主线程是单线程的, 同步代码顺序执行;
   - 异步任务(定时器、Promise、IO、事件)不会"插队"打断同步代码, 而是排队, 由事件循环调度。

2. 两类异步任务队列:
   - 宏任务(macrotask/task): setTimeout、setInterval、setImmediate(Node)、I/O、UI事件、整体script;
   - 微任务(microtask/job): Promise.then/catch/finally、await之后的代码、queueMicrotask、MutationObserver。

3. 事件循环的执行规则(顺序的关键):
   ① 执行完当前所有同步代码;
   ② 清空【整个】微任务队列(期间新产生的微任务也一起清, 直到空);
   ③ 从宏任务队列取【一个】执行;
   ④ 回到②: 再清空所有微任务; 再取下一个宏任务... 循环往复。
   → 结论: 微任务永远先于(下一个)宏任务; 微任务一次清空, 宏任务一次一个。

4. 由此推出的常见现象:
   - Promise.then 先于 setTimeout(0)(微先于宏);
   - 同步代码先于一切异步;
   - setTimeout(fn, 0) 不是立即执行: 是"尽快入宏任务队列", 要等同步+微任务跑完, 还有最小延迟(~4ms);
   - 大量微任务会"饿死"宏任务(微任务里不断产生微任务, 宏任务一直轮不上)。

5. 实务要点:
   - 别用 setTimeout(0) 去"等某个异步/渲染完成"或"排时序"——不可靠;
     要等就用对应的机制: 等Promise用await/then、等DOM渲染用requestAnimationFrame/相应API;
   - 想"让出一下主线程、稍后继续"用 queueMicrotask(微任务) 或 setTimeout(宏任务), 清楚二者时机不同;
   - 理解 await: await后面的代码相当于放进微任务, 在当前同步代码后、宏任务前执行。

6. 本质: 异步的执行顺序, 由一套明确的"调度规则 + 任务分类优先级"决定, 不由你的书写顺序决定
   - "写在前面"不代表"先执行"; "延迟0"不代表"最先";
   - 要按事件循环的规则去推断顺序, 而非凭"书写顺序/延迟数字"的直觉。

一句话: JS单线程靠事件循环调度, 异步分宏任务(setTimeout/IO)和微任务(Promise/await), 每轮先清空所有微任务
   再取一个宏任务、微任务永远先于宏任务、setTimeout(0)非立即; 别凭书写顺序或setTimeout(0)想当然排时序。

这套认知,是整个坑的根。单线程 + 事件循环:JS 主线程单线程,异步任务排队由事件循环调度,不插队打断同步两类队列:宏任务(setTimeout/IO/事件)、微任务(Promise.then/await/queueMicrotask)执行规则:跑完同步→清空整个微任务队列→取一个宏任务→再清空所有微任务→……;微任务永远先于(下一个)宏任务、一次清空,宏任务一次一个常见现象:Promise.then 先于 setTimeout(0)、同步先于一切异步、setTimeout(0) 非立即、大量微任务会饿死宏任务实务:别用 setTimeout(0) 排时序/等渲染;await 后续代码是微任务本质:异步执行顺序由调度规则+任务分类决定,不由书写顺序——"写在前面"≠"先执行"、"延迟 0"≠"最先"一句话:JS 单线程靠事件循环调度,异步分宏任务(setTimeout/IO)和微任务(Promise/await),每轮先清空所有微任务再取一个宏任务、微任务永远先于宏任务、setTimeout(0) 非立即;别凭书写顺序或 setTimeout(0) 想当然排时序。

第二件事:正解——用对的机制控制时序,别靠 setTimeout(0)

知道了事件循环规则,正解就清楚了:要等什么就用对应的机制,别用 setTimeout(0) 投机。

// 正解1: 等一个异步操作完成 → 用 await / then(而不是 setTimeout 猜它好了没)
// ✗ 错误: 用 setTimeout 猜数据加载好了
// loadData(); setTimeout(() => useData(), 0);   // 不可靠! 0不保证数据已好
// ✓ 正确: 明确等待它完成
const data = await loadData();   // 数据真的好了, 再用
useData(data);

// 正解2: 想"让出主线程、稍后继续", 按需选微任务或宏任务(清楚时机)
queueMicrotask(() => doSoon());      // 微任务: 当前同步后、下个宏任务前执行(更"早")
setTimeout(() => doLater(), 0);      // 宏任务: 让出更彻底, 排在微任务之后(更"晚")
// 二者时机不同, 按需要选; 别以为它俩一样。

// 正解3: 等 DOM 渲染/绘制完成 → 用 requestAnimationFrame, 别用 setTimeout
// ✗ el.style.x = ...; setTimeout(() => measure(el), 0);  // 不保证已绘制
// ✓ el.style.x = ...; requestAnimationFrame(() => measure(el));  // 下一帧绘制后

// 正解4: 理解 await 的时序(await后续是微任务)
async function f() {
  console.log("a 同步");
  await something();          // await处暂停, 后续放进微任务
  console.log("c 微任务");    // 在当前同步代码跑完后、宏任务前执行
}
f();
console.log("b 同步");
// 输出: a, b, c  (a同步 → b同步 → c微任务)

// 正解5: 避免微任务饿死宏任务/避免无限微任务
//   - 别在微任务里无限地再产生微任务(会让宏任务/渲染一直轮不上, 页面卡死);
//   - 重活拆成宏任务分批做(setTimeout/MessageChannel), 给渲染和其他任务机会。

// 正解6: 推断顺序就按规则走, 别凭直觉
//   同步 → 清空所有微任务 → 一个宏任务 → 清空所有微任务 → ...; 按这个推就不会错。

// 核心: 要等异步完成用await/then、等渲染用requestAnimationFrame、让出主线程按需选微/宏任务;
//   理解await后续是微任务; 别用setTimeout(0)投机排时序; 推断顺序严格按事件循环规则。

这套正解的关键,是用"对的机制"精确表达"我要等什么、什么时候执行",而非用 setTimeout(0) 去碰运气等异步完成用 await/then:别用 setTimeout 猜它好了没——这正是本次该改的。让出主线程按需选微/宏任务:queueMicrotask 更早、setTimeout 更晚,时机不同。等渲染用 requestAnimationFrame:别用 setTimeout(0)。理解 await 后续是微任务:在同步后、宏任务前执行。避免微任务饿死宏任务:别在微任务里无限产生微任务,重活拆成宏任务分批。核心是:推断顺序严格按"同步→清空微任务→一个宏任务→……"的规则走,别凭直觉。

第三件事:其他几个"误判执行顺序/时机"的坑

顺着这次事件循环,我把"误判异步/并发执行时机"的几类坑也一并理了:

几类"误判执行顺序/时机"的坑:

坑1: 用 setTimeout(0) 当"立即"或"排序"——它是宏任务、非立即, 还在微任务后(上文);
   正解: 用await/then/requestAnimationFrame等对应机制。

坑2: forEach 里 await 不按预期串行(同351)——forEach不等await, 回调并发跑、顺序乱;
   正解: for...of + await 串行, 或 Promise.all 并发但等齐。

坑3: 以为 Promise 构造器里的代码是异步的——new Promise(executor)里的executor是【同步】立即执行的,
   只有.then回调才是微任务; 别搞混。

坑4: 多个await并发还是串行没分清——连续await是串行(一个等完再下一个);
   要并发用 Promise.all([p1,p2]); 别把本可并发的写成串行(慢)。

坑5: 依赖异步回调里的循环变量(var闭包, 类似Go544)——回调执行时循环已结束、变量是终值;
   正解: 用let(块作用域)或传参。

坑6: 假设异步一定按发起顺序返回——多个异步请求, 返回顺序不保证和发起顺序一致;
   正解: 需要顺序就await串行或按标识匹配结果, 别假设先发先回。

共同的根: 异步/并发代码的"执行顺序和时机", 由【运行时的调度规则】(事件循环、宏微任务、并发)决定,
   而【不由代码的书写顺序】决定; 凭"写在前面就先跑""延迟短就先跑""先发就先回"的直觉去推断异步时序,
   几乎必错; 要理解背后的调度规则, 用明确的机制(await/Promise.all/对应API)来表达真正的时序依赖。

这些坑看似不同,根却是同一个:异步/并发代码的"执行顺序和时机",由运行时的调度规则(事件循环、宏微任务)决定,而不由代码的书写顺序决定;凭"写在前面就先跑、延迟短就先跑、先发就先回"的直觉去推断异步时序,几乎必错认清这个根("异步时序看调度规则、不看书写顺序,用明确机制表达时序依赖"),才能写出顺序正确的异步代码。

第四件事:宏任务 vs 微任务 / 时序控制——两张对照表

我把宏任务、微任务的分类和时机,以及该用什么机制控制时序,整理成对照表,贴在了团队的 JS 规范里:

任务类型 典型来源 执行时机
同步代码 普通语句 最先,顺序执行
微任务 Promise.then、await 后续、queueMicrotask 同步后,清空整队,先于宏任务
宏任务 setTimeout、setInterval、I/O、UI 事件 微任务清空后,一次取一个
渲染 浏览器绘制 宏任务间隙,rAF 在绘制前
setTimeout(fn,0) 宏任务 非立即,还有最小延迟 ~4ms
需求 该用 别用
等异步操作完成 await / then setTimeout(0) 猜
等 DOM 绘制完 requestAnimationFrame setTimeout(0)
多个异步并发等齐 Promise.all 多个 await 串行(慢)
串行多个异步 for...of + await forEach + async
尽早让出主线程 queueMicrotask
彻底让出/分批重活 setTimeout / MessageChannel

这两张表的核心,第一张是记住执行顺序:同步 → 微任务(清空整队)→ 宏任务(一次一个),setTimeout(0) 在最后且非立即;第二张是要控制时序,用对应的明确机制(await/rAF/Promise.all),别用 setTimeout(0) 投机。记住一条:需要"等某件事"时,用"能确切知道那件事完成"的机制去等,而不是用 setTimeout(0) 赌它该好了。

第五件事:关于事件循环的几组容易想当然的认知

这次事故也让我厘清了几组关于异步执行顺序的、容易想当然的概念:

直觉以为 实际上
setTimeout(0) 是立即/最先执行 是宏任务,在同步和所有微任务之后
异步按书写顺序执行 按事件循环调度规则,与书写顺序无关
延迟短的先执行 微任务无延迟也先于 0 延迟的宏任务
Promise 构造器里的代码是异步的 executor 是同步立即执行的
多个 await 是并发的 连续 await 是串行,要并发用 Promise.all
用 setTimeout(0) 能等渲染/数据 不可靠,用 rAF / await
异步先发的先返回 返回顺序不保证,需要顺序要显式控制

这张表里,我栽的是第一行和第二行:以为"setTimeout(0) 立即/最先"、"异步按书写顺序跑",完全不知道有宏/微任务两个队列和事件循环的调度规则,于是想靠 setTimeout(0) 排时序,全乱厘清这些,核心是一个意识:JS 的异步执行顺序,是由"事件循环 + 宏/微任务分类"这套明确规则决定的,不是由你代码的书写顺序或延迟数字决定的;要控制时序,就用能确切表达"等什么、何时执行"的机制(await/then/rAF/Promise.all),而不是凭直觉用 setTimeout(0) 去赌。

第六件事:写异步 / 推断执行顺序时,我现在的自检习惯

现在每当我写异步代码、或要推断它的执行顺序,我都会先按这张图问自己:

这张图的精髓,是"等异步用await、等渲染用rAF、并发用Promise.all、推顺序按事件循环规则"先分控制时序还是推断顺序、控制就用对应明确机制、推断就按同步→微任务→宏任务规则走这套习惯,让我从"用 setTimeout(0) 投机排序"变成了"按规则用对的机制表达时序"——核心始终是:JS 异步分宏/微任务、每轮先清空所有微任务再取一个宏任务、微任务永远先于宏任务、setTimeout(0) 非立即;异步顺序由调度规则决定而非书写顺序,用 await/then/rAF/Promise.all 表达真正的时序依赖。

我立下的几条规矩

这场"setTimeout(0) 总在 Promise 之后、时序全乱"的事故,换来了我写 JS 异步时,刻进骨子里的几条铁律:

  1. JS 单线程靠事件循环调度异步;异步分宏任务(setTimeout/IO/事件)和微任务(Promise/await/queueMicrotask)。
  2. 每轮:跑完同步 → 清空整个微任务队列 → 取一个宏任务 → 再清空所有微任务 → 循环。
  3. 微任务永远先于(下一个)宏任务;Promise.then 先于 setTimeout(0)。
  4. setTimeout(fn, 0) 不是立即执行,是宏任务、还有最小延迟,别拿它当"马上/最先"。
  5. 等异步完成用 await/then,等渲染用 requestAnimationFrame,别用 setTimeout(0) 投机排时序。
  6. 多异步并发等齐用 Promise.all,串行用 for...of + await(别 forEach+async)。
  7. 异步执行顺序由调度规则决定、不由书写顺序;推断顺序严格按事件循环规则走。

附:一道经典的事件循环顺序题(自测)

借这次的坑,我整理了一道把宏/微任务、await 都揉在一起的经典顺序题,贴在内部 wiki 当自测——能一眼说对顺序,基本就吃透事件循环了。

console.log("1");

setTimeout(() => console.log("2"), 0);          // 宏任务

Promise.resolve().then(() => {
  console.log("3");                              // 微任务
  Promise.resolve().then(() => console.log("4")); // 微任务里再产生的微任务, 本轮一起清
});

(async () => {
  console.log("5");                              // 同步(async函数体在await前是同步的)
  await null;
  console.log("6");                              // await后续 → 微任务
})();

console.log("7");

// 正确顺序: 1, 5, 7, 3, 6, 4, 2
// 推导:
//   同步阶段: 1 → 5(async体await前) → 7;
//   清空微任务: 3(第一个then) → 6(await后续) → 4(then里又产生的微任务, 同轮清掉);
//   取一个宏任务: 2(setTimeout)。
// 关键体会:
//   - 5是同步的(async函数在第一个await前同步执行); 6才是微任务;
//   - 微任务队列会"一直清到空"(连带3里产生的4也在本轮清), 之后才轮到宏任务2;
//   - setTimeout(0)的2, 不管延迟多小, 都排在所有微任务后面。

这道题的价值,在于它把"同步、微任务、await 后续、微任务里再生的微任务、宏任务"全揉在一起,逼你严格按事件循环规则一步步推,而不是凭书写顺序猜。我发现,能不能一口说对这道题的顺序(1,5,7,3,6,4,2),恰恰是"真懂事件循环"和"以为自己懂"的分水岭。把它当自测题,比背十遍"微任务先于宏任务"都管用。

写在最后

回头看,这场由"误以为 setTimeout(0) 立即执行、异步按书写顺序跑"引发的、时序全乱的事故,真正教给我的,远不止"微任务先于宏任务"这一个知识点。它让我对"一堆事情'什么时候、按什么顺序发生', 往往不取决于'我以为的、表面的顺序(谁写在前面、谁延迟短)', 而取决于背后一套'我没看见、却实实在在主宰着调度的规则'; 凭表面直觉去推断, 必然和真实发生的顺序对不上",有了一次刻骨的体会。我栽跟头,是因为我用"表面的、直觉的顺序"(写在前面的先跑、延迟 0 的最快)去推断"实际的执行顺序"——我以为顺序是"所见即所得"的;可我没看见的是: 这堆异步任务背后, 有一个'事件循环'在按一套确定的规则(微任务优先、宏任务排队)默默地调度它们;真正决定"谁先谁后"的, 是这套我一开始根本不知道存在的'底层调度规则', 而不是我代码表面的书写顺序; 我拿着错误的模型(书写顺序=执行顺序)去预测, 自然全错这让我领悟到一个关于"表象顺序与底层规则"的深刻认知:很多系统里, 事情发生的"实际顺序/时机", 是由一套底层的、常常不可见的'调度/优先级/规则'机制决定的; 而这套规则, 往往和"表面的、直觉的顺序"(提交顺序、书写顺序、先来后到)不一致;如果你不去理解这套底层规则, 只凭"表面看起来该怎样"的直觉去推断和依赖, 就会在"真实顺序≠直觉顺序"的地方反复栽跟头——而且因为它"不报错、只是顺序怪", 你还很难发现问题出在哪;真正可靠的做法是: 去搞懂那套底层规则(它如何调度、谁优先), 用'能确切表达依赖关系'的方式(而非'靠表面顺序碰运气')来构建你需要的时序这给了我一种面对"顺序与时机"时的清醒:每当我依赖"一堆事情按某个顺序发生"时,要追问"决定它们顺序的, 是我以为的'表面顺序', 还是背后有一套我没搞懂的'调度规则'?"——不满足于"看起来该这样", 而去理解真正主宰顺序的底层机制; 并用"明确表达时序依赖"的手段(await、显式编排), 而非"赌表面顺序"(setTimeout(0))来保证我要的顺序;"理解底层调度规则、用明确依赖而非表面顺序来构建时序",是写对一切异步/并发/有时序要求之代码的关键认清实际顺序由底层调度规则决定而非表面书写顺序、凭直觉推断异步时序必错、要理解规则并用明确机制表达时序依赖——这,是我用一次事件循环的事故,换来的、关于 JavaScript、也关于如何理解表象与底层规则的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次想用 setTimeout(0) "排个时序"之前,先想想那个微任务永远插在它前面的事件循环、改用 await 把依赖写明白,那我对着那个"输出顺序和我写的完全对不上"的现场困惑的这段时间,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我在 Python 里用 is 来判断两个数是否相等,小数值时一直好好的,某次数值大了一点 is 突然返回 False,因为 is 比的根本不是值相等而是是不是同一个对象,只是小整数被缓存了才碰巧一致的深度复盘

2026-6-3 1:42:46

技术教程

我在 Go 里用 == 判断一个错误是不是记录不存在,一直好好的,直到某层代码用 %w 把这个错误包装了一下再往上抛,我的 == 判断就突然失效了,把记录不存在当成了未知错误的深度复盘

2026-6-3 1:53:49

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索