在日常的开发中,经常会遇到这样一个场景:
一个页面中有许多的用户头像,为了减少服务端的并发压力,服务端会提供一个批量请求接口。前端批量请求后再通过逻辑做单个用户与头像的关系匹配。
eg:
js
getUserAvatar('zhangsan')
getUserAvatar('lisi')
const res = fetchAll(['zhangsan','lisi'])
res = {
'zhangsan':{
avatar:'',
name:'章三'
}
// 没有lisi代表lisi不存在
}
// 要能做到通过以上两个接口分最终获取到的是一个promise,
// 但是实际上只请求一次服务端。共同调用fetchAll接口
首先,可以确定的是,要有一个function
返回的是一个promise,getUserAvatar
就要返回一个promise
,且这个promise
要在fetchAll
结果返回之后被置为resolve
或者reject
的状态。
那么getUserAvatar
是有以下实现:
js
function getUserAvatar(id){
return new Promise((res,rej)=>{
})
}
接下来要考虑的是getUserAvatar
被调用后如何触发接口请求?什么时候执行promsie
的resolve
或者reject
。
首先要满足以下几点:
- 100ms内只发一次请求
promise
的执行结果要和当前传入的id
一一对应。
满足第一点:只发一次请求,是一个类似于节流的实现。那就要用到setTimout
实现节流的这一套逻辑。
js
getAvatarCloser(deley){
let timmer;
return function(id){
if(!timmer){
timmer = setTimout(()=>{
fetchAll()
clearTimout(timmer);
timmer = null;
},[deley])
}
}
}
const getUserAvatar= getAvatarCloser(100)
这时候的节流已经处理完毕,但是发现这个时候的getUserAvatar
还不能获取到正确的结果,因为fetchAll
是一个批量请求接口,这里要怎么把每次调用getUserAvatar
传进来的参数组合到一起呢?当然是合理利用节流函数创建的闭包了!利用闭包保存每次调用的参数,在setTimout
执行的时候全部取出。
js
getAvatarCloser(deley){
let timmer;
let requestSet = new Set() // 合理利用set去重
return function(id){
if(!requestSet.has(id)){ // 如果还没有请求过,就加入requestSet.等待请求
requestSet.add(id)
}
if(!timmer){
timmer = setTimout(async ()=>{
const userInfos = await fetchAll(Array.from(requestSet.values()));
clearTimout(timmer);
timmer = null;
},[deley])
}
}
}
const getUserAvatar= getAvatarCloser(100)
这时候,获取到了全部的数据,要怎么给对应的调用返回呢??(满足第二点)
分析一下:
每个promise
都需要延迟 到一个异步请求结束后在执行。怎么做到延迟
执行呢?
即:在promise
外部执行promise的resolve
方法
那把promise
的resolve
和reject
方法的引用保存在promise
外面不就可以做到了吗?
js
class DeferredPromise{
resolve; // 调用这个resolve就是调用promise的resolve
reject;
promise;
constructore(){
this.promise = new Promise((res,rej)=>{
this.resolve = res;
this.reject = rej;
})
}
}
利用deferred
。当deferred
的resolve
被执行的时候,相当于deferred.promise.resolve
被执行。
js
getAvatarCloser(deley){
let timmer;
let requestSet = new Set() // 合理利用set去重
let resultMap = new Map();
return function(id){
// 保证每次调用getUserAvatar返回的都是一个新的promise
return new Promise((res,rej)=>{
if(!requestSet.has(id)){ // 如果还没有请求过,就加入requestSet.等待请求
requestSet.add(id)
resultMap.add(id,new DeferredPromise())
}
if(!timmer){
timmer = setTimout(async ()=>{
const keys = Array.from(requestSet.values())
const userInfoRes = await fetchAll(keys);
keys.forEach((id) => {
const deferred = deferredMap.get(id);
if (userInfoRes[id]) {
requestSet.delete(+id);
// 相当于执行deferred.promise.resolve.
deferred.resolve(userInfoRes[id]);
} else {
deferred.reject(new Error(`没找到${id}`));
}
});
clearTimout(timmer);
timmer = null;
},[deley])
}else{
const currentDef = deferredMap.get(id);
// 获取currentDef.promise的结果。并执行当前的promise.resolve
currentDef.promise.then((data)=>{
res(rs)
})
.catch((e)=>{ rej(e) })
}
})
}
}
const getUserAvatar= getAvatarCloser(100)