一个小细节 -- 接口节流 和 Async函数

203 阅读2分钟

前段时间维护一个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())

测试了下,效果确实如此,每次最多同时发出两个请求

screenshot-20240830-161012.png

开始思索

首先,把焦点定位在传入的 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)

果然,执行后变成,所有请求同时发出,什么原因呐?

screenshot-20240830-163520.png

稍加思索

又仔细看了下代码,发现vscode提示错误 不能将类型“Promise”分配给类型“number”

screenshot-20240830-170646.png

脑袋里忽然想到,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 上,终于算是理解了,完结🎉。