Daily-Interview-Question
Daily-Interview-Question copied to clipboard
第182题:实现一个异步求和函数
提供一个异步 add
方法如下,需要实现一个 await sum(...args)
函数:
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 1000);
}
function asyncAdd(a, b, callback) {
setTimeout(() => {
callback(null, a + b)
}, 1000)
}
// 封装一个promise版的add函数
function add(a, b) {
return new Promise(resolve => {
asyncAdd(a, b, (_, sum) => {
resolve(sum)
})
})
}
function sum(...args) {
return new Promise(resolve => {
args.reduce((p, n) => p.then(total => add(total, n)), Promise.resolve(0)).then(resolve)
})
}
;(async () => {
const result = await sum(1, 2, 3, 4)
console.log(result)
})()
简化:两数之和
我们先来简单的实现一个异步两数之和函数
function sumT(a, b) {
return await new Promise((resolve, reject) => {
asyncAdd(a, b, (err, res) => {
if(!err) {
resolve(res)
}
reject(err)
})
})
}
// 测试
const test = await sumT(1, 2)
console.log(test)
// 3
加深:多数之和
上面我们实现了两数之和,然后扩展到多数之和喃?
提到数组求和问题,我们首先想到的是 reduce
reduce()
方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。—— MDN
arr.reduce(callback(acc, cur[, idx[, arr]])[, initialValue])
callback
函数接收4个参数:
-
acc
:累计器 -
cur
:当前值 -
idx
: 当前索引 -
arr
:源数组
其中, initialValue
可选,
- 如果有
initialValue
:acc
取值为initialValue
,cur
取数组中的第一个值 - 如果没有:
acc
取数组中的第一个值,cur
取数组中的第二个值
const arr = [1, 2, 3, 4];
const reducer = (acc, cur) => acc + cur;
// 1 + 2 + 3 + 4
console.log(arr.reduce(reducer));
// 输出: 10
// 5 + 1 + 2 + 3 + 4
console.log(arr.reduce(reducer, 5));
// 输出: 15
关于本题:来自@champkeh
设置初始值为 Promise.resolve(0)
,经历 5
次求和:
function sum(...args) {
return new Promise(resolve => {
args.reduce((acc, cur) => acc.then(total => sumT(total, cur)), Promise.resolve(0)).then(resolve)
})
}
// 测试
await sum(1, 2, 3, 4, 5)
// 15
但这存在一个耗时较长的问题,我们可以计算下时间:
console.time("sum")
// 测试
await sum(1, 2, 3, 4, 5)
// 15
console.timeEnd("sum")
也就是说,我们每次求和都会花费 1s,串行异步求和,这显然不是最优的
优化:使用 Promise.all
我们可以两两一组,使用 Promise.all
求和,再把和两两一组继续求和…..,知道只剩余一个就是最终的结果
async function sum(...args) {
// 用于考察每次迭代的过程
console.log(args)
// 如果仅有一个,直接返回
if(args.length === 1) return args[0]
let result = []
// 两两一组,如果有剩余一个,直接进入
for(let i = 0; i < args.length - 1; i+=2) {
result.push(sumT(args[i], args[i + 1]))
}
if(args.length%2) result.push(args[args.length-1])
// Promise.all 组内求和
return sum(...await Promise.all(result))
}
// 测试
test = await sum(1, 2, 3, 4, 5)
// 15
console.time("sum")
await sum(1, 2, 3, 4, 5)
console.timeEnd("sum")
function asyncAdd(a, b) {
return new Promise((res, rej) => {
setTimeout(() => {
res(a + b);
}, Math.random() * 1000);
});
}
function sum() {
let args = [...arguments];
let count = 0;
function checkArray(arr, res) {
if (arr.length >= 2) {
count += 1;
asyncAdd(...arr.splice(0, 2)).then(result => {
count -= 1;
arr.push(result);
checkArray(arr, res);
});
checkArray(arr, res);
} else {
if (count === 0) {
res(arr[0]);
}
}
}
return new Promise((res, rej) => {
if (args.length === 1) {
res(args[0]);
} else {
checkArray(args, res);
}
});
}
sum(1, 2, 3, 4, 5).then(result => {
console.log(result);
});
大佬们,这个是不是会快一点,Promise.all如果两组同时进行的话,其中一组快,一组慢的话就会出现快的那组等待慢组执行完才能去跟后面的相加,但是这块时间本来可以利用起来跟后面的先进行计算的,Promise.all有点浪费多余时间了吧,求大佬指点!
function asyncAdd() {
return new Promise((resolve, reject) => {
try {
resolve([].slice.call(arguments).reduce((sum, item, index, originArr) => {
if (typeof item !== 'number') throw new Error('参数不是纯数字')
return sum + item
}, 0))
} catch (error) {
reject(error)
}
})
}
asyncAdd(1, 2, 3, 10).then(res => console.log(res)) // 3
不晓得是不是这个意思哦 ,可以传入参数列表
const log = console.log.bind(console);
function add(a, b, cb) {
setTimeout(() => {
cb(a + b);
}, 100);
}
let sumTwo = (a, b) => {
return new Promise((resolve) => {
add(a, b, resolve);
});
};
function sum(...args) {
return new Promise((resolve) => {
let total = args.reduce((prev, cur) => {
if (prev instanceof Promise) {
return prev.then((res) => sumTwo(res, cur));
}
return sumTwo(prev, cur);
});
resolve(total);
});
}
async function sum2(...args) {
log(args);
if (args.length === 1) {
return args[0];
}
let promiseList = [];
for (let i = 0; i < args.length - 1; i += 2) {
promiseList.push(sumTwo(args[i], args[i + 1]));
}
if (args.length % 2 !== 0) {
promiseList.push(args[args.length - 1]);
}
let resList = await Promise.all(promiseList);
return sum2(...resList);
}
async function test() {
console.time("耗时");
let arr = new Array(10).fill(0).map((x, index) => {
return 10 ** index;
});
let res = await sum2(...arr);
console.log("final", res);
console.timeEnd("耗时");
}
async function test2() {
// console.log(this.name);
console.time("耗时2");
let arr = new Array(10).fill(0).map((x, index) => {
return 10 ** index;
});
let res = await sum(...arr);
console.log("final", res);
console.timeEnd("耗时2");
}
test();
test2();
// 实现一个异步求和函数
// 提供一个异步 add 方法如下,需要实现一个 await sum(...args) 函数:
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 1000);
}
function asyncAddProm(a, b) {
return new Promise((rs, rj) => {
asyncAdd(a, b, (err, res) => {
if (err) {
rj(err);
} else {
rs(res);
}
});
});
}
function sum(...args) {
return args.reduce((acc, cur) => acc.then(total => asyncSum(cur, total)), Promise.resolve(0));
}
async function main() {
console.time("calc")
const res = await sum(1, 2, 3, 4, 5);
console.log(res);
console.timeEnd("calc")
}
main();
使用 Promise.all
:
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 1000);
}
function promiseAdd(a, b) {
return new Promise((resolve) => {
asyncAdd(a, b, (_, sum) => resolve(sum));
});
}
async function sum(...args) {
const promises = [];
for (let i = 0; i < args.length; i += 2) {
if (typeof args[i + 1] !== 'undefined') {
promises.push(promiseAdd(args[i], args[i + 1]));
} else {
promises.push(Promise.resolve(args[i]));
}
}
const result = await Promise.all(promises);
if (result.length === 1) return result[0];
return sum(...result);
}
(async () => {
console.time('time');
console.log(await sum(1, 2, 3, 4, 5));
console.timeEnd('time');
})();
// 提供一个异步 add 方法如下,需要实现一个 await sum(...args) 函数:
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b);
}, 1000);
}
function add(a, b) {
return new Promise((resolve) => {
asyncAdd(a, b, (_, sum) => {
resolve(sum);
});
});
}
// 方法1
function sum1(...args) {
return new Promise((resolve) => {
resolve(
args.reduce(async (p, c) => await add(await Promise.resolve(p), c), 0)
);
});
}
// 方法2
function sum2(...args) {
return new Promise((resolve) => {
args
.reduce((p, c) => Promise.resolve(p).then((total) => add(total, c)), 0)
.then(resolve);
});
}
// 使用
(async function () {
const result1 = await sum1(1, 2, 3, 4);
const result2 = await sum12(1, 2, 3, 4);
console.log(result1, result2);
})();
分享一个更简洁的写法
function asyncAdd(a, b, callback) {
setTimeout(function() {
callback(null, a + b)
}, 1000)
}
function sum() {
const queue = Array.from(arguments);
let sum = queue.shift();
return new Promise((resolve, reject) => {
function next() {
if (queue.length > 0) {
asyncAdd(sum, queue.shift(), (err, res) => {
console.log("%c Line:16 🌽 res", "color:#fca650", res);
sum = res;
next();
})
} else {
resolve(sum);
}
}
next();
});
}
sum(1,2,3,4,5,6,7,8,9,10,11).then(sum => console.log(sum))
// Line:16 🌽 res 3
// Line:16 🌽 res 6
// Line:16 🌽 res 10
// Line:16 🌽 res 15
// Line:16 🌽 res 21
// Line:16 🌽 res 28
// Line:16 🌽 res 36
// Line:16 🌽 res 45
// Line:16 🌽 res 55
// Line:16 🌽 res 66
// 66
我的实现思路就是二分法,不停地二分,直到满足终止条件。
function asyncAdd(a = 0, b = 0, callback) {
setTimeout(function() {
callback(a + b)
}, 1000)
}
const add = (a = 0, b = 0) => new Promise((resolve) => asyncAdd(a, b, resolve));
function sum(...args) {
if (args.length === 1) return new Promise((resolve) => resolve(args));
if (args.length === 2) {
return new Promise((resolve) => asyncAdd(args[0], args[1], resolve));
}
let params = [];
for (let i = 0; i < args.length; i += 2) {
if (i + 1 < args.length) {
params.push([args[i], args[i + 1]]);
} else {
params.push([args[i]]);
}
}
let fns = []
params.forEach(([a, b]) => {
fns.push(add(a, b));
});
return Promise.allSettled(fns).then((res) => {
const answers = res.map(item => item.value);
console.log(answers)
return sum(...answers);
})
}