前端Promise手写题解析与面试实战技巧

📅 发布时间:2026/8/24 3:01:28
前端Promise手写题解析与面试实战技巧
1. 为什么Promise手写题是前端面试的必考项最近两年在面试候选人时我发现90%的前端面试都会考察Promise相关的手写实现。这背后反映的是现代前端开发的两个基本事实首先异步编程已成为前端开发的日常从数据请求到状态管理都离不开Promise其次真正理解Promise工作机制才能避免在实际项目中踩坑。我整理出最常考的4道带for循环的Promise手写题这些题目既能考察对Promise基础机制的理解又能检验循环与异步结合的处理能力。下面我们就从事件循环机制讲起逐步拆解每道题的实现要点。2. Promise基础与事件循环机制回顾2.1 Promise的三种状态与特点Promise对象具有pending、fulfilled和rejected三种状态且状态变更不可逆。这里有个容易忽略的细节then方法返回的是新Promise对象这构成了链式调用的基础。const p new Promise((resolve) { resolve(1) // 状态变为fulfilled }) p.then(v v1) // 返回新Promise .then(v console.log(v)) // 输出22.2 事件循环中的执行顺序理解下面这段代码的输出顺序是面试常见考点console.log(script start) setTimeout(() { console.log(setTimeout) }, 0) Promise.resolve().then(() { console.log(promise1) }).then(() { console.log(promise2) }) console.log(script end)正确输出顺序是script startscript endpromise1promise2setTimeout这是因为微任务Promise then回调会在当前宏任务结束后立即执行而setTimeout属于宏任务。3. 高频手写题一顺序执行Promise数组3.1 题目要求实现一个函数serialExecute按顺序执行Promise数组前一个执行完再执行下一个const tasks [ () Promise.resolve(1), () Promise.resolve(2), () Promise.resolve(3) ] serialExecute(tasks).then(console.log) // 期望输出[1,2,3]3.2 实现方案对比常见错误是直接用forEach或map遍历这会导致Promise并行执行。正确做法是利用reduce串联function serialExecute(tasks) { return tasks.reduce((prev, curr) { return prev.then(results curr().then(value [...results, value]) ) }, Promise.resolve([])) }3.3 避坑指南初始值必须是Promise.resolve([])否则无法调用then每次迭代需要返回新Promise形成链式调用结果数组需要通过扩展运算符保持不可变性4. 高频手写题二带并发限制的Promise执行4.1 题目场景实际项目中经常需要控制并发请求数量避免服务器压力过大const urls [url1, url2, url3, url4] const fetch url Promise.resolve(data from ${url}) parallelLimit(urls, fetch, 2).then(console.log) // 期望最多同时2个请求4.2 核心实现思路使用递归计数器的方式控制并发function parallelLimit(list, fn, limit) { let active 0 let index 0 const results [] return new Promise((resolve) { function run() { while (active limit index list.length) { active const current index fn(list[current]).then(res { results[current] res active-- run() }) } if (active 0 index list.length) { resolve(results) } } run() }) }4.3 性能优化技巧使用while循环而非forEach更精确控制并发通过闭包保存每个Promise的结果位置递归调用时机选择在Promise settled后5. 高频手写题三实现Promise.retry超时重试5.1 业务需求分析对于不稳定的接口请求通常需要实现自动重试机制function fetchData() { return Math.random() 0.5 ? Promise.resolve(success) : Promise.reject(fail) } retry(fetchData, 3).then(console.log, console.error)5.2 递归实现方案通过递归实现简洁明了function retry(fn, times, delay 1000) { return new Promise((resolve, reject) { const attempt () { fn().then(resolve) .catch(err { if (times-- 0) { setTimeout(attempt, delay) } else { reject(err) } }) } attempt() }) }5.3 进阶优化方向支持指数退避算法增加重试间隔添加超时取消功能允许自定义重试条件判断6. 高频手写题四实现Promise.all的竞速版本6.1 与Promise.all的区别传统Promise.all需要等待所有Promise完成而竞速版本只要有一个成功就返回const promises [ delay(100).then(() 1), delay(200).then(() 2), delay(50).then(() 3) ] raceAll(promises).then(console.log) // 最快返回36.2 实现关键点利用Promise.race的机制function raceAll(promises) { return new Promise((resolve, reject) { let finished false promises.forEach(p { p.then( val !finished (finished true) resolve(val), err !finished (finished true) reject(err) ) }) }) }6.3 边界情况处理空数组应该直接reject需要处理所有Promise都reject的情况添加取消功能避免内存泄漏7. 面试实战技巧与避坑指南7.1 白板编码注意事项先明确输入输出类型写出基础用例再编码主动解释代码思路7.2 常见扣分点未处理reject情况循环变量作用域问题忘记返回Promise链7.3 性能优化方向减少不必要的Promise创建合理使用微任务队列避免内存泄漏在实际项目中我曾遇到一个Promise链内存泄漏的案例由于未正确取消Pending状态的Promise导致页面切换时内存持续增长。后来通过添加AbortController解决了这个问题。这提醒我们手写Promise不仅要考虑功能实现还要关注实际工程中的各种边界情况。