前段时间维护一个node项目时,突然发现以下一段代码,乍一看感觉像是要实现接口请求的节流效果,wtf?这是什么操作,不确定,我得研究下
const list = Array.from({ length: 10 }, (_, i) => i + 1)
const getUserInfo = (id: number) => {
return axios.get('getUserById?id=' + id)
}
lodash.chunk(list, 2).reduce(async (pre, cur) => {
await pre
await Promise.all(cur.map((id) => getUserInfo(id)))
return pre
}, Promise.resolve())
测试了下,效果确实如此,每次最多同时发出两个请求
开始思索
首先,把焦点定位在传入的 Promise.resolve() 上,它又赋值给pre,可pre从头到尾没咋使用,索性把Promise.resolve()改为 0 传入,测试下发现,没有变化,依然可以达到节流效果。
那既然如此,直接把pre替换成 0,代码改成这样
lodash.chunk(list, 2).reduce(async (pre, cur) => {
await 0
await Promise.all(cur.map((id) => getUserInfo(id)))
return 0
}, 0)
果然,执行后变成,所有请求同时发出,什么原因呐?
稍加思索
又仔细看了下代码,发现vscode提示错误 不能将类型“Promise”分配给类型“number”
脑袋里忽然想到,async函数的返回值总是一个 promise,由于return 0,此时函数返回值的类型为 Promise<number>,而在 reduce 函数初始化时的赋值为 0,为number类型,两者类型不一致。
既然如此,那改成下面👇这样呐,总归编译器不提示错误了吧。
lodash.chunk(list, 2).reduce(async (pre, cur) => {
await Promise.resolve()
await Promise.all(cur.map((id) => getUserInfo(id)))
return Promise.resolve()
}, Promise.resolve())
可依然不行🙅,所有接口请求全部发出,到底什么原因呐?
再加思索
开始怀疑reduce函数内部是不是有特殊机制?于是自己写了个myreduce简单测试了下。
Array.prototype.myReduce = function(func, first) {
let len = this.length;
let start = 0;
if(first === undefined) {
start = 1; first = this[0];
}
let ans = first; for(let i = start; i < len; i++) {
ans = func(ans, this[i], i, this);
}
return ans;
}
结果是自己写的myReduce,也能起到节流效果。也就是说并不是reduce理解的有误,原因还是那段代码没完全读懂。
最后
终于在下面这段代码中找到了端倪,这里的ans是上一个函数的返回值,它是一个promise,而在func函数里,由于await pre的原因,第二次执行时必须等待上一次函数执行完成。
ans = func(ans, this[i], i, this);
所以,整个reduce函数执行的过程,可以简单理解成
const func = async () => {
await Promise.all(cur.map((id) => getUserInfo(id)))
}
await func()
await func()
await func()
await func()
// ....
所以,问题的关键就在这不起眼的 await pre 上,终于算是理解了,完结🎉。