一、为什么 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 都能顺藤摸瓜。