JS实现Ajax并发请求控制:批量请求函数multiRequest解析
本文深入解析字节跳动面试中关于JS实现Ajax并发请求控制的经典题目。通过对比串行与并行请求,重点阐述如何利用Promise.all结合递归调用实现最大并发数限制。文章提供multiRequest函数的完整实现思路与代码,确保在控制内存溢出的同时,按原始URL顺序返回结果。
前言
近期在技术与职业规划上感到些许迷茫,为此咨询了多位在大厂工作的朋友,希望能获取一些关于未来发展的建议。在交流中,话题涉及到了面试环节以及招聘过程中常考的题目。鉴于自己许久未参与面试,便从中挑选了几道具有代表性的题目进行整理。后续将陆续发布一系列针对这些面试题的详细解析文章。
本文选取的题目源自字节跳动的面试案例:
需要实现一个批量请求函数 multiRequest(urls, maxNum),具体需求如下:
• 设定最大并发数 maxNum
• 每当有一个请求返回,就释放一个空位,从而允许发起新的请求
• 所有请求完成后,结果需严格按照 urls 数组中的原始顺序依次输出
这道题目在面试中较为常见,下文将从应用场景、问题分析到最终代码实现,层层递进,力求深入浅出地给出完整解析。
应用场景
假设存在一种场景:需要发送 30 个异步请求,但出于性能或服务器限制,必须将同一时刻的并发请求数量控制在 5 个以内,同时要求尽可能快速地获取响应结果。
该如何实现?
首先,我们需要了解 Ajax 请求的串行与并行概念。
基于 Promise.all 实现 Ajax 的串行和并行
日常开发中,我们通常基于 Promise 来封装异步请求,本文也将围绕异步请求展开讨论。
- 串行:指一个异步请求执行完毕后,再开始执行下一个请求
- 并行:指多个异步请求同时发起并执行
通过定义若干 Promise 实例来具体演示串行与并行的区别。
串行示例
var p = function () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log("1000");
resolve();
}, 1000);
});
};
var p1 = function () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log("2000");
resolve();
}, 2000);
});
};
var p2 = function () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log("3000");
resolve();
}, 3000);
});
};
p()
.then(() => {
return p1();
})
.then(() => {
return p2();
})
.then(() => {
console.log("end");
});
如示例代码所示,串行执行会从上到下依次调用对应的接口请求。
并行示例
通常,当我们需要在多个异步处理完成后执行后续代码时,会用到:
Promise.all((promises: [])).then((fun: function));
Promise.all可以保证,promises数组中所有promise对象都达到resolve状态,才执行then回调。
var promises = function () {
return [1000, 2000, 3000].map((current) => {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(current);
}, current);
});
});
};
Promise.all(promises()).then(() => {
console.log("end");
});
Promise.all 并发限制
此时考虑一个极端场景:如果 promises 数组中的每个对象都是 HTTP 请求,且此类对象多达几十万个。
在这种情况下,瞬间发出几十万个 HTTP 请求,极可能导致调用栈堆积,进而引发内存溢出。
因此,我们需要对 Promise.all 进行并发限制处理。
Promise.all 并发限制的核心在于:每个时刻并发执行的 Promise 数量保持固定,而最终的执行结果仍需与原始 Promise.all 保持一致。
题目实现
思路分析
整体采用递归调用来实现:初始阶段发送的请求数量上限为允许的最大值 maxNum,且这些请求中的每一个在完成时都应继续递归发起新请求。通过传入的索引来确定 urls 数组中具体的 URL,从而保证最后输出的顺序与 urls 一致,不会乱序。
代码实现
function multiRequest(urls = [], maxNum) {
// 请求总数量
const len = urls.length;
// 根据请求数量创建一个数组来保存请求的结果
const result = new Array(len).fill(false);
// 当前完成的数量
let count = 0;
return new Promise((resolve, reject) => {
// 请求maxNum个
while (count < maxNum) {
next();
}
function next() {
let current = count++;
// 处理边界条件
if (current >= len) {
// 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
!result.includes(false) && resolve(result);
return;
}
const url = urls[current];
console.log(`开始 ${current}`, new Date().toLocaleString());
fetch(url)
.then((res) => {
// 保存请求结果
result[current] = res;
console.log(`完成 ${current}`, new Date().toLocaleString());
// 请求没有全部完成, 就递归
if (current < len) {
next();
}
})
.catch((err) => {
console.log(`结束 ${current}`, new Date().toLocaleString());
result[current] = err;
// 请求没有全部完成, 就递归
if (current < len) {
next();
}
});
}
});
}
总结
到此这篇关于字节跳动面试之如何用JS实现Ajax并发请求控制的文章就介绍到这了,更多相关JS实现Ajax并发请求控制内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
