JavaScript 异步与事件循环:宏任务、微任务与 await

一、为什么 JS 是单线程却能并发

JavaScript 在浏览器/Node 里是单线程执行的,但它靠事件循环把耗时 IO 交给底层线程,等好了再回调,从而”看起来”并发。核心三件套:调用栈、宏任务队列、微任务队列。

二、宏任务 vs 微任务

类型 例子 优先级
微任务 Promise.then / catch / finally、queueMicrotask 每轮宏任务后清空
宏任务 setTimeout、setInterval、I/O、UI 事件 下一轮事件循环

规则:一个宏任务执行完,先清空所有微任务,再取下一个宏任务。所以 Promise.then 总比 setTimeout(fn,0) 先跑。

三、一段必考题

console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 输出顺序:1 4 3 2

解析:同步代码 1、4 先出;微任务 3 在宏任务 2 之前。

四、async / await 的本质

await 后面的代码等价于 .then 里的回调,是微任务。注意下面两种写法的差别:

// 串行:两次 await 顺序等
const a = await f1();
const b = await f2();

// 并行:先发起再一起等
const [a, b] = await Promise.all([f1(), f2()]);

五、常见坑

  • 忘记 await:返回的是 Promise 而非值,后续计算拿到 undefined;
  • 在循环里 await:变成串行,量大时用 Promise.all;
  • 吞掉异常:async 函数抛错返回 rejected Promise,记得 try/catch 或 .catch。

搞懂”同步→微任务→宏任务”的执行顺序,JS 异步的大部分面试题和线上 bug 都能顺藤摸瓜。