前言:最近闲来无事,刷刷面试题。遇到了一道经典的promise并发的面试题,并写出解题思路分享以下
题目如下:
/**
* 最大请求并发数,返回Promise
* @param {string[]} urls 请求urls数组
* @param {number} maxNum 最大请求数量
* @returns {Promise<any[]>} Promise
*/
function concurRequest(urls, maxNum) {} // 完成函数主体部分
这道题主要是考察对promise的理解深度
弄明白函数的输入参数和输出如下图:
输入是urls字符串数组和最大并发数maxNum
输出是一个异步的promise,这个promise只有一个resolve,因为要等待所有的urls结束(成功或失败),所以要有个数组results保存每个请求结束的结果
function concurRequest(urls, maxNum) {
return new Promise(resolve => {
// 当urls数组为0时直接结束
if (urls.length === 0) {
resolve([]);
return;
}
const results = []; // 存储每个请求的结果
});
}
这里先判断下边界情况之一,当urls数组为空,没必要发请求了,直接返回空结果
然后定义发请求函数request
function concurRequest(urls, maxNum) {
return new Promise(resolve => {
// 当urls数组为0时直接结束
if (urls.length === 0) {
resolve([]);
return;
}
const results = []; // 存储每个请求的结果
let index = 0; // 下一个请求的下标
// 发出请求
async function request() {
const i = index; // 记录当前发出请求的下标
const url = urls[index];
index++; // 每发出一个请求,下标加1
console.log(url);
try {
const value = await fetch(url);
results[i] = value;
} catch (error) {
results[i] = error;
} finally {
request(); // 每个请求结束后继续发请求
}
}
// 测试
request();
request();
request();
});
}
这里写一个html文件,注册下urls数组如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>测试</title>
</head>
<body>
<script src="./concurRequest.js"></script>
<script>
const urls = [];
for (let i = 1; i < 100; i++) {
urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
}
concurRequest(urls, 20).then(resp => {
console.log(resp);
});
</script>
</body>
</html>
由于测试没用到maxNum,可以看到:
这里请求超出100后还在发出,是因为没有做继续发出请求限制条件,加上即可
async function request() {
// 当请求等于urls数组长度,结束不在继续发请求了
if(index===urls.length){
return;
}
...
到此,发送请求函数完成差不多了,不过还没有对返回的promise的状态做定论,这个状态肯定是resolved,也就是不管请求成功还是失败,结果都应该存储在results数组中,并且下标对应,然后等到全部请求都结束在resolve(results),如下:
async function request() {
// 当请求等于urls数组长度,结束不在继续发请求了
if (index === urls.length) {
return;
}
const i = index; // 记录当前发出请求的下标
const url = urls[index];
index++; // 每发出一个请求,下标加1
try {
const value = await fetch(url);
results[i] = value;
} catch (error) {
results[i] = error;
} finally {
count++;
// 当完成请求的数量等于urls的长度时,结束
if (count === urls.length) {
resolve(results);
}
request(); // 每个请求结束后继续发请求
}
}
最后就是同时并发多个请求,并发数是urls的长度与maxNum的最小值,然后for循环运行request,这里考虑到for循环里的多个请求发出不是同时的,由于请求发出是异步的,认为之间差异时间很小,所以算是同时发出多个请求
// 取urls的长度和与maxNum的最小値,发出请求
const times = Math.min(maxNum, urls.length);
for (let i = 0; i < times; i++) {
request();
}
测试截图如下,这里设置的maxNum为3
可以把网络速度调慢点儿更明显
至此,完成了这道题目,总的来说还是加深了对promise的理解,完整代码如下:
test.html
文件
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>测试</title>
</head>
<body>
<script src="./concurRequest.js"></script>
<script>
const urls = [];
for (let i = 1; i < 10; i++) {
urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
}
concurRequest(urls, 3).then(resp => {
console.log(resp);
});
</script>
</body>
</html>
concurRequest.js
文件
/**
* 最大请求并发数,返回Promise
* @param {string[]} urls 请求urls数组
* @param {number} maxNum 最大请求数量
* @returns {Promise<any[]>} Promise
*/
function concurRequest(urls, maxNum) {
return new Promise(resolve => {
// 当urls数组为0时直接结束
if (urls.length === 0) {
resolve([]);
return;
}
const results = []; // 存储每个请求的结果
let index = 0; // 下一个请求的下标
let count = 0; // 请求完成的数量,包括成功和失败
// 发出请求
async function request() {
// 当请求等于urls数组长度,结束不在继续发请求了
if (index === urls.length) {
return;
}
const i = index; // 记录当前发出请求的下标
const url = urls[index];
index++; // 每发出一个请求,下标加1
try {
const value = await fetch(url);
results[i] = value;
} catch (error) {
results[i] = error;
} finally {
count++;
// 当完成请求的数量等于urls的长度时,结束
if (count === urls.length) {
resolve(results);
}
request(); // 每个请求结束后继续发请求
}
}
// 取urls的长度和与maxNum的最小値,发出请求
const times = Math.min(maxNum, urls.length);
for (let i = 0; i < times; i++) {
request();
}
});
}