Promise高频场景题+手撕代码

张开发
2026/4/14 21:58:24 15 分钟阅读

分享文章

Promise高频场景题+手撕代码
结合拼多多、小红书、牛客网的前端面试真题我整理了Promise 高频场景题 手撕代码覆盖拼多多核心考点并发控制、API 实现、重试机制、小红书业务场景批量请求、错误容错、牛客网笔试题串行执行、特殊 API全部是面试可直接背的标准答案。一、拼多多前端核心 Promise 题一面/二面必考场景题 1Promise 并发控制最常考题目实现一个promisePool函数限制最大并发数max谁先完成就补下一个结果顺序与输入一致全部成功才 resolve任一失败立即 reject。适配「电商商品批量库存查询」「订单批量提交」场景。解析核心是维护运行任务数用索引控制任务分配避免重复执行。手撕代码/** * 并发控制 Promise 池 * param {Array() Promise} tasks 异步任务数组必须是返回 Promise 的函数 * param {number} max 最大并发数 * returns {Promiseany[]} 结果数组顺序与 tasks 一致 */functionpromisePool(tasks,max){if(!Array.isArray(tasks)||max0)returnPromise.reject(newError(参数非法));constresultsnewArray(tasks.length);// 固定长度保证顺序letrunning0;// 正在运行的任务数letindex0;// 下一个要执行的任务索引letisRejectedfalse;// 标记是否有任务失败returnnewPromise((resolve,reject){// 执行下一个任务construnNext(){// 所有任务已执行完且无运行中任务 → 全部完成if(indextasks.lengthrunning0){returnresolve(results);}// 并发数未满且还有任务 → 执行while(runningmaxindextasks.length){constcurrentIndexindex;consttasktasks[currentIndex];running;// 执行任务捕获成功/失败Promise.resolve(task()).then(res{if(isRejected)return;// 已失败不再处理结果results[currentIndex]res;}).catch(err{if(isRejected)return;isRejectedtrue;reject(err);// 任一失败立即 reject}).finally((){running--;runNext();// 任务完成补下一个});}};runNext();// 启动执行});}// 测试用例电商库存查询场景constmockTasks[()newPromise(ressetTimeout(()res({skuId:1,stock:10}),1000)),()newPromise(ressetTimeout(()res({skuId:2,stock:0}),500)),()newPromise(ressetTimeout(()res({skuId:3,stock:20}),800)),()newPromise(ressetTimeout(()res({skuId:4,stock:5}),300)),];// 并发数 2模拟电商批量查询promisePool(mockTasks,2).then(console.log)// 顺序输出[{skuId:1,stock:10}, {skuId:2,stock:0}, {skuId:3,stock:20}, {skuId:4,stock:5}].catch(console.error);场景题 2Promise.retry 重试机制拼多多暑期实习笔试题题目实现一个Promise.retry方法失败后自动重试times次每次重试间隔delay毫秒重试成功则返回结果全部失败则 reject。适配「支付接口重试」「网络波动请求重试」场景。手撕代码/** * Promise 重试机制 * param {() Promise} fn 要执行的异步函数返回 Promise * param {number} times 重试次数包含第一次执行默认 3 次 * param {number} delay 重试间隔毫秒默认 1000 * returns {Promiseany} 最终结果 */Promise.retryfunction(fn,times3,delay1000){returnnewPromise((resolve,reject){// 定义重试函数constattempt(count){fn().then(resolve)// 成功直接 resolve.catch(err{// 重试次数用尽 → rejectif(counttimes){returnreject(newError(重试${times}次全部失败${err.message}));}// 延迟重试setTimeout((){attempt(count1);},delay);});};attempt(1);// 第一次执行});};// 测试用例支付接口重试constpayRequest()newPromise((resolve,reject){Math.random()0.3?resolve(支付成功):reject(newError(支付接口超时));});// 重试 3 次间隔 500msPromise.retry(payRequest,3,500).then(console.log)// 支付成功概率 70%.catch(console.error);// 重试 3 次失败时触发手撕题 1Promise.all 实现全平台必考题目手写实现Promise.all要求与原生行为一致传入可迭代对象全部成功 resolve 结果数组顺序与输入一致任一失败立即 reject空数组返回空数组兼容非 Promise 值。手撕代码/** * 手写 Promise.all * param {Iterable} iterable 可迭代对象数组、Set 等 * returns {Promiseany[]} 结果数组 */functionPromiseAll(iterable){// 校验可迭代对象if(!iterable||!iterable[Symbol.iterator]){returnPromise.reject(newTypeError(参数必须是可迭代对象));}constarrArray.from(iterable);constlenarr.length;constresultsnewArray(len);letcompletedCount0;returnnewPromise((resolve,reject){// 空数组直接 resolveif(len0)returnresolve(results);arr.forEach((item,index){// 包装成 Promise兼容普通值、thenablePromise.resolve(item).then(res{results[index]res;completedCount;// 全部完成 → resolveif(completedCountlen){resolve(results);}}).catch(err{// 任一失败 → 立即 rejectreject(err);});});});}// 测试用例PromiseAll([1,Promise.resolve(2),newPromise(ressetTimeout(()res(3),100))]).then(console.log);// [1,2,3]PromiseAll([1,Promise.reject(错误),3]).catch(console.error);// 错误手撕题 2Promise.race 实现拼多多常考变形题题目手写实现Promise.race要求与原生行为一致返回第一个完成成功/失败的 Promise 的结果/原因。手撕代码/** * 手写 Promise.race * param {Iterable} iterable 可迭代对象 * returns {Promiseany} 第一个完成的结果 */functionPromiseRace(iterable){if(!iterable||!iterable[Symbol.iterator]){returnPromise.reject(newTypeError(参数必须是可迭代对象));}constarrArray.from(iterable);returnnewPromise((resolve,reject){// 空迭代器 → 永远 pendingif(arr.length0)return;arr.forEach(item{Promise.resolve(item).then(resolve)// 第一个成功的直接 resolve.catch(reject);// 第一个失败的直接 reject});});}// 测试用例constp1newPromise(ressetTimeout(()res(p1 完成),100));constp2newPromise(ressetTimeout(()res(p2 完成),50));PromiseRace([p1,p2]).then(console.log);// p2 完成二、小红书前端 Promise 场景题业务场景适配小红书侧重批量请求容错、异步流程设计以下是 2 个高频业务题。场景题 1小红书笔记批量发布Promise.allSettled 应用题目小红书笔记发布时需同时上传 5 张图片要求① 全部上传完成后再提交笔记② 部分图片上传失败不影响整体流程需记录失败原因③ 最终返回所有图片的上传结果成功/失败。请实现该逻辑。手撕代码/** * 小红书笔记图片批量上传 * param {ArrayFile} files 图片文件数组 * returns {PromiseArray{status: fulfilled/rejected, url?: string, reason?: Error}} 上传结果 */asyncfunctionuploadNoteImages(files){// 模拟上传接口constuploadApi(file)newPromise((resolve,reject){setTimeout((){// 模拟 20% 失败率Math.random()0.2?resolve(https://xiaohongshu.com/image/${Date.now()}):reject(newError(图片${file.name}上传失败));},500Math.random()*500);});// 用 allSettled 处理容错不因为单张失败终止整体constresultsawaitPromise.allSettled(files.map(fileuploadApi(file)));// 格式化结果适配小红书业务端展示returnresults.map((result,index)({status:result.status,url:result.statusfulfilled?result.value:undefined,reason:result.statusrejected?result.reason:undefined,fileName:files[index].name}));}// 测试用例constmockFiles[newFile([],image1.jpg),newFile([],image2.jpg),newFile([],image3.jpg),];uploadNoteImages(mockFiles).then(console.log).catch(console.error);场景题 2小红书用户关注列表分页加载Promise 串行执行题目小红书关注列表分页加载需按顺序请求第 1、2、3… 页每一页请求依赖上一页的cursor且同一时间只能有一个请求在执行。请实现该串行逻辑。手撕代码/** * 小红书关注列表串行分页请求 * param {number} pageSize 每页数量 * param {number} maxPage 最大请求页数 * returns {PromiseArrayany} 所有页的关注列表数据 */functionfetchFollowList(pageSize10,maxPage5){letcursor0;// 分页游标constallData[];// 存储所有页数据letcurrentPage1;// 模拟小红书关注列表接口constfollowApi(cursor,pageSize)newPromise((resolve){setTimeout((){// 模拟数据每页 10 条第 3 页开始无数据constdatacurrentPage3?Array.from({length:pageSize},(_,i)({userId:user_${cursori1},name:用户${cursori1},avatar:https://avatar.com/${cursori1}})):[];resolve({data,nextCursor:cursorpageSize});},300);});// 串行执行分页请求constfetchPageasync(){if(currentPagemaxPage)returnallData;const{data,nextCursor}awaitfollowApi(cursor,pageSize);allData.push(...data);cursornextCursor;currentPage;// 无数据或达到最大页数 → 结束if(data.length0||currentPagemaxPage){returnallData;}// 串行请求下一页returnfetchPage();};returnfetchPage();}// 测试用例fetchFollowList(10,5).then(console.log)// 输出前 2 页数据共 20 条.catch(console.error);三、牛客网 Promise 笔试题笔试高频牛客网侧重Promise 细节理解、特殊 API 实现、循环中 Promise 陷阱以下是 3 道必刷题。笔试题 1lastPromise 实现拼多多笔试真题题目实现lastPromise函数传入 Iterable 参数返回最后一个成功的 Promise 的结果失败的 Promise 跳过若所有 Promise 都失败返回字符串all promise is reject。手撕代码/** * 牛客网笔试题lastPromise * param {Iterable} iterable 可迭代对象 * returns {Promiseany | string} 最后一个成功结果全失败返回 all promise is reject */functionlastPromise(iterable){if(!iterable||!iterable[Symbol.iterator]){returnPromise.reject(newTypeError(参数必须是可迭代对象));}constarrArray.from(iterable);if(arr.length0)returnPromise.resolve(all promise is reject);// 包装所有任务捕获失败只保留成功结果constsuccessPromisesarr.map(itemPromise.resolve(item).catch(()null)// 失败返回 null);returnPromise.all(successPromises).then(results{// 过滤成功结果constsuccessResultsresults.filter(resres!null);if(successResults.length0){// 返回最后一个成功结果returnsuccessResults[successResults.length-1];}else{// 全失败returnall promise is reject;}});}// 测试用例lastPromise([newPromise(ressetTimeout(()res(9),2000)),1,// 普通值直接成功newPromise((_,rej)rej(2)),// 失败Promise.reject(3)// 失败]).then(console.log);// 1最后一个成功结果lastPromise([Promise.reject(1),Promise.reject(2)]).then(console.log);// all promise is reject笔试题 2Promise 串行执行任务牛客网高频题目实现serialExecuteTasks函数传入异步任务数组每个任务是返回 Promise 的函数串行执行前一个完成后执行后一个全部成功返回结果数组任一失败立即 reject。手撕代码/** * 牛客网笔试题Promise 串行执行 * param {Array() Promise} tasks 异步任务数组 * returns {Promiseany[]} 结果数组 */asyncfunctionserialExecuteTasks(tasks){if(!Array.isArray(tasks))returnPromise.reject(newError(参数必须是数组));constresults[];for(consttaskoftasks){// 串行执行等待前一个完成constresawaittask();results.push(res);}returnresults;}// 测试用例consttasks[()newPromise(ressetTimeout(()res(1),1000)),()newPromise(ressetTimeout(()res(2),500)),()newPromise(ressetTimeout(()

更多文章