前言
今天是大年初一,祝大家新年快乐,新的一年技术水平不断提升,工作顺心顺意,都升职加薪,写代码没bug。大年初一是放松心情,尽情玩耍的日子,但是这个假期也别忘了提升自己的技术哦。
今天我们就来聊一聊手写支持设置最大并发数的promise处理函数。这应该是一个比较常见的面试题,之前笔者也在面试某大厂的时候遇到过这个codding
的题目。废话不多说,我们进入正题
题目
手写一个函数,该函数接收两个函数,一个是promise
数组,另一个是设置的最大并发数;当所有的promise
对象都处理完成之后,返回一个包含了所有结果的数组,结果数组顺序需要与原始promise
数组顺序一样;如果某一个promise
对象reject
了,也要把reject
原因放入结果数组中
分析
首先,我们分析这个题目的入参出参,入参已经确定,出参肯定返回一个新的Promise
对象,并且这个对象一定是resolve
的;所以可以先把函数整体框架搭好;
function concurrentPromise(promises,max){
return new Promise(reslove=>{
// 详细代码
})
}
其次,我们考虑一些异常场景。当传入的第一个参数不是数组,或者是数组但不是promise
数组时是我们针对promise
的一些处理是会有异常的,所以针对入参我们需要做一些校验;
function concurrentPromise(promises,max){
// 如果传入的不是一个数组,抛出异常
if (!Array.isArray(promises)) {
throw new Error("请传入promise数组");
}
// 如果传入数组中不全是Promise对象,抛出异常
const isAllPromise = promises.every((v) => {
return v instanceof Promise;
});
if (!isAllPromise) {
throw new Error("请传入promise数组");
}
if (typeof max !== "number") {
throw new Error("请传入正确的最大并发数");
}
return new Promise(reslove=>{
// 详细代码
})
}
再次,对于promise
对象,我们肯定是一个一个单独处理,处理完一个才处理下一个,所以我们需要一个函数来处理单个的promise
对象,然后对于当前是处理的哪个promise
对象也需要做好标记,因此,我们可以先写出如下代码:
function concurrentPromise(promises,max){
// 如果传入的不是一个数组,抛出异常
if (!Array.isArray(promises)) {
throw new Error("请传入promise数组");
}
// 如果传入数组中不全是Promise对象,抛出异常
const isAllPromise = promises.every((v) => {
return v instanceof Promise;
});
if (!isAllPromise) {
throw new Error("请传入promise数组");
}
if (typeof max !== "number") {
throw new Error("请传入正确的最大并发数");
}
return new Promise(reslove=>{
// 存放所有promise对象结束pending状态后的结果
const result = [];
// 当前处理的promise对象的下标
let index = 0;
// 已经处理的promise对象的个数
let count = 0;
// 处理单个promise的方法
async function handlePromise() {
// 如果所有的promise对象都已经取出处理过了,那么就直接返回
if (index >= len) {
return;
}
// 取出将要处理的promise对象
const promise = promises[index];
// 存储当前处理的promise对象在数组中的索引,放入result数组中时需要对应上
const i = index;
// 处理完这个promise对象后,下标需要加1,下一次就调用函数时需要取出下一个promise对象去处理
index++;
try {
const res = await promise.then();
// 拿到结果之后将结果存入result数组中,下标要对应上
result[i] = res;
} catch (error) {
// 就算promise对象reject了,也需要把reject原因存入result数组中
result[i] = error;
} finally {
// 每处理完一个promise对象,count都需要自增1
count++;
// 如果所有的promise对象都处理完了,那么把结果resolve出去即可
if (count === len) {
resolve(result);
} else {
// 处理完这个promise对象时,调用函数处理下一个promise对象
handlePromise();
}
}
}
})
}
最后,我们只需要考虑一开始需要调用几次handlePromise
方法,然后一开始的时候调用几次就好;因为考虑到max > promises.length
的场景,只需要调用promise.length
次即可,代码实现如下:
// 初始化调用handlePromise函数的次数,有可能max比数组长度大
const times = Math.min(len, max);
for (let i = 0; i < times; i++) {
handlePromise();
}
完整代码实现
function concurrentPromise(promises, max) {
// 如果传入的不是一个数组,抛出异常
if (!Array.isArray(promises)) {
throw new Error("请传入promise数组");
}
// 如果传入数组中不全是Promise对象,抛出异常
const isAllPromise = promises.every((v) => {
return v instanceof Promise;
});
if (!isAllPromise) {
throw new Error("请传入promise数组");
}
if (typeof max !== "number") {
throw new Error("请传入正确的最大并发数");
}
// 返回一个Promise对象
return new Promise((resolve) => {
const len = promises.length;
if (len === 0) {
resolve([]);
}
// 存放所有promise对象结束pending状态后的结果
const result = [];
// 当前处理的promise对象的下标
let index = 0;
// 已经处理的promise对象的个数
let count = 0;
// 处理单个promise的方法
async function handlePromise() {
// 如果所有的promise对象都已经取出处理过了,那么就直接返回
if (index >= len) {
return;
}
// 取出将要处理的promise对象
const promise = promises[index];
// 存储当前处理的promise对象在数组中的索引,放入result数组中时需要对应上
const i = index;
// 处理完这个promise对象后,下标需要加1,下一次就调用函数时需要取出下一个promise对象去处理
index++;
try {
const res = await promise.then();
// 拿到结果之后将结果存入result数组中,下标要对应上
result[i] = res;
} catch (error) {
// 就算promise对象reject了,也需要把reject原因存入result数组中
result[i] = error;
} finally {
// 每处理完一个promise对象,count都需要自增1
count++;
// 如果所有的promise对象都处理完了,那么把结果resolve出去即可
if (count === len) {
resolve(result);
} else {
// 处理完这个promise对象时,调用函数处理下一个promise对象
handlePromise();
}
}
}
// 初始化调用handlePromise函数的次数,有可能max比数组长度大
const times = Math.min(len, max);
for (let i = 0; i < times; i++) {
handlePromise();
}
});
}