近几天面试被问了一道服务器求和的问题,当时是循环解决的方式来完成的,虽然答上来了,但感觉可能并不是最优解,因为近两天HR并没有联系🐶;
那么我们可以看一下,是不是有更好的方式来解决,然后我们再看一下相关的同类问题,学习中归纳和分类可以帮助我们更好的融汇贯通;
原题:
const addRemote = (left, right) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(left + right);
}, 1000);
});
};
const add = (...inputs) => {} // 需要完成的部分
我们可以使用循环来解决,如下;
const add = async (...inputs) => {
if (inputs.length === 1) {
return inputs[0];
}
let result = inputs[0];
for (let i = 1; i < inputs.length; i++) {
result = await addRemote(result, inputs[i]);
}
return result;
};
但是这样可以发现,输出结果很慢有,因为这么写这些异步操作是串行的;所以我们下面来实现另一种更加灵活的方式:
我们可以把问题分解成两两归一的问题;例如将输入参数分成两个一组,一轮循环可以使参数长度减半;最终它们会将长度收敛成1,也就是最终结果;这种我们可以并发请求;
可能还有一个问题,就是可能是奇数个个数的数组,我们可能需要特殊处理,但是我们可以使用一个补位将问题转换为标准问题(学过数学的很多解题方式是这样的,把陌生问题转换靠到自己已经解决的问题);
我们先看一下版本一:
const add = async (...inputs) => {
let combineInputs = [];
if (inputs.length === 1) {
return inputs[0];
}
const isOdd = inputs.length % 2;
if (isOdd) {
inputs.push(0);
}
for (let i = 0; i + 1 < inputs.length; i += 2) {
combineInputs.push([inputs[i], inputs[i + 1]]);
}
return Promise.all(combineInputs.map((item) => addRemote(...item))).then(
(adds) => {
return add(...adds);
}
);
};
这种是我们通过Promise的递归来解决的,我们还可以用await+循环方式来解决;
const add = async (...inputs) => {
if (inputs.length === 1) {
return inputs[0];
}
let results = inputs;
do {
let combineInputs = [];
const isOdd = results.length % 2;
if (isOdd) {
results.push(0);
}
for (let i = 0; i + 1 < results.length; i += 2) {
combineInputs.push([results[i], results[i + 1]]);
}
results = await Promise.all(
combineInputs.map((item) => addRemote(...item))
);
} while (results.length > 1);
return results[0];
};
那么我们在冰河时代下这么答就稳了么,其实我们可以再拓展下,比如如果做了20个数的加法,后端要求不要把10多个请求一下子发过去,是不是可以控制一下;
所以我们可以包裹下Promise.all, 用controlParallel 来控制下;
const controlParallel = async (inputs, count) => {
let result = [];
for (let i = 0; i < inputs.length; i += count) {
let subResult = await Promise.all(
inputs.slice(i, i + count).map((item) => addRemote(...item))
);
result = result.concat(subResult);
}
return result;
};
这样我们就可以通过count参数来控制并发请求的数量了,调成1, 相当于我们开头的解法(只能说相当于),进一步如果想并发的请求数量可以在接口交互时协商取得count, 动态修正循环的步进长度;
最终版代码如下(菜狗只能搞到这了),大家有不同意见可以放评论区哈,不一定是最好的答案哈(仅做参考);
const controlParallel = async (inputs, count) => {
let result = [];
for (let i = 0; i < inputs.length; i += count) {
let subResult = await Promise.all(
inputs.slice(i, i + count).map((item) => addRemote(...item))
);
result = result.concat(subResult);
}
return result;
};
const getAdd = parallelCount => async (...inputs) => {
if (inputs.length === 1) {
return inputs[0];
}
let results = inputs;
do {
let combineInputs = [];
const isOdd = results.length % 2;
if (isOdd) {
results.push(0);
}
for (let i = 0; i + 1 < results.length; i += 2) {
combineInputs.push([results[i], results[i + 1]]);
}
results = await controlParallel(combineInputs, parallelCount);
} while (results.length > 1);
return results[0];
};
const add = getAdd(3);
add(1).then((result) => {
console.log({ result });
});
add(1, 8, 9, 21, 20, 0).then((result) => {
console.log({ result });
});