> 技术文档 > 从手写 Promise 到 Event Loop:前端异步面试题全解读

从手写 Promise 到 Event Loop:前端异步面试题全解读

你真的搞懂了 JS 的异步吗?面试官一个“nextTick 和微任务的区别”,别说新手,连三年经验的工程师也常答不上来。


🚪 开场:异步机制,就是面试里的“送命题”

你是不是也有过这样的经历:

“你能手写一个 Promise 吗?”
“你知道 async/await 背后是怎么工作的?”
“setTimeout(fn, 0) 一定最先执行吗?”

听起来是基础题,但很多人答得云里雾里。异步是 JS 的灵魂,但也因为它涉及 事件循环、任务队列、原型链、浏览器调度等多维概念,导致很多人学完就忘,项目里“会用”,但面试时讲不明白。

今天我们就试图从“手写 Promise”这个最容易落地的入口,一路讲清楚 JS 异步编程的核心机制,帮你构建一个 系统化、具备输出力 的答题结构。


🧩 一、手写 Promise:不是背代码,而是理解状态流转

👇 面试问题原型

“请实现一个简易的 Promise 类,支持 then 链式调用。”

这个问题考的不只是代码实现,更是你对“状态管理 + 异步调度 + 任务队列”的理解。

🛠️ 简易实现(ES6 版本)

class MyPromise { constructor(executor) { this.state = \'pending\'; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (val) => { if (this.state === \'pending\') { this.state = \'fulfilled\'; this.value = val; this.onFulfilledCallbacks.forEach(cb => cb(val)); } }; const reject = (err) => { if (this.state === \'pending\') { this.state = \'rejected\'; this.reason = err; this.onRejectedCallbacks.forEach(cb => cb(err)); } }; try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { const fulfilledFn = (val) => { try { const x = onFulfilled ? onFulfilled(val) : val; resolve(x); } catch (e) { reject(e); } }; const rejectedFn = (err) => { try { const x = onRejected ? onRejected(err) : err; reject(x); } catch (e) { reject(e); } }; if (this.state === \'fulfilled\') { setTimeout(() => fulfilledFn(this.value)); } else if (this.state === \'rejected\') { setTimeout(() => rejectedFn(this.reason)); } else { this.onFulfilledCallbacks.push(() => setTimeout(() => fulfilledFn(this.value))); this.onRejectedCallbacks.push(() => setTimeout(() => rejectedFn(this.reason))); } }); }}

✨ 讲解亮点

  • 状态流转:Promise 只能从 pendingfulfilledrejected,不可逆。

  • then 需要异步执行,所以用了 setTimeout(模拟 microtask)。

  • 链式调用是通过 return new Promise 实现的。

如果你能清楚讲出“状态驱动、异步调度、回调存储”这三点,基本就能给面试官留下印象。


🔄 二、事件循环 Event Loop:异步机制的底层引擎

👇 面试问题原型

“说说 JS 的 Event Loop 执行顺序?”
“微任务和宏任务的区别?”
“为什么 await 后的代码是微任务?”

🧠 面试答题模板

你可以照这个顺序作答:

JavaScript 是单线程模型,事件循环机制(Event Loop)保证主线程不断检查任务队列并执行。队列主要分为两种:

  • 宏任务(macro task):setTimeout、setInterval、setImmediate、script(整体代码)

  • 微任务(micro task):Promise.then、MutationObserver、queueMicrotask

每次事件循环 tick:

  1. 执行一个宏任务(如 script)

  2. 清空所有微任务队列(按顺序执行)

  3. 渲染 DOM(浏览器)

  4. 开启下一个宏任务

🧪 示例验证:

console.log(\'script start\');setTimeout(() => console.log(\'timeout\'), 0);Promise.resolve().then(() => { console.log(\'promise1\');}).then(() => { console.log(\'promise2\');});console.log(\'script end\');

✅ 输出顺序

script startscript endpromise1promise2timeout

📌 一句话总结

Promise 回调属于微任务队列,会在当前宏任务执行完后立即执行;而 setTimeout 属于下一个宏任务。


🧵 三、async/await:语法糖还是异步陷阱?

👇 面试问题原型

“async/await 背后到底做了什么?”
“try/catch 为啥能捕获 await 的异常?”

其实 async/await 是 Generator + Promise 的语法糖,执行过程等价于“自动化的 then 链式调用 + 错误捕获”。

🤹 举个经典例子

async function test() { console.log(\'start\'); await Promise.resolve().then(() => console.log(\'after await\')); console.log(\'end\');}test();

✅ 输出顺序

startafter awaitend

🌟 面试答题亮点

  • await 会暂停当前 async 函数的执行,等 Promise resolve 后再继续执行剩下的代码。

  • await 后的语句会被加入微任务队列,紧接着执行。


💡 四、进阶场景:nextTick、MessageChannel、浏览器 vs Node

“在 Node.js 中,setImmediate 和 process.nextTick 谁先执行?”

Node.js 有自己的事件循环模型,其中 process.nextTick 比微任务还要早:

setImmediate(() => console.log(\'immediate\'));process.nextTick(() => console.log(\'nextTick\'));Promise.resolve().then(() => console.log(\'promise\'));

✅ 输出顺序(Node.js)

nextTickpromiseimmediate

但在浏览器中,nextTick 不存在,使用 queueMicrotaskMessageChannel 实现更精细控制。


🧭 总结一句话

面试异步问题,不只是考你“能不能跑对”,而是考你能不能 讲明白执行机制 + 展现结构化思维


📮 结语与社交引导

异步,是前端面试中最容易“一知半解”的部分。理解手写 Promise 能帮你打好基础,掌握事件循环让你回答不再含糊,而 async/await 则是把这些基础串联成系统的关键。

如果你看到这里还没点赞支持一下,那我真的要怀疑你是不是在背题了 🙈
欢迎加我威 atar24,备注【异步专题】,拉你进群,一起刷面试、刷原理、搞清楚事件循环到底绕了几圈!