在我们的工程中,很有可能碰到一种情况那就是不同的组件几乎在同样的时间加载然后用到了同样的url和参数,然后造成了同样的请求,所以我们可以设置一个缓存接口,这个缓存接口调用后会返回一个函数,我们再通过这个函数去请求具体的后台,所以使用起来是这样的

const request = window_it(20)  //20ms之内
request("....").then(resp=>{    //请求1
    console.log(resp)
}) 
request("....").then(resp=>{    //请求2
    console.log(resp)
})复制代码

这个window_it的实现我们需要给一个时间窗口,在20ms内相同的请求就直接只发一条,所以实现起来是这样的,代码简写,重点看个思路

function window_it(time){
    let map = new Map()
    let flag = true
    return function request(...args){
          return new Promise((reslve,reject)=>{
                let key = hash(...args)     //用参数来拿到hash
                if(!map.has(key)){
                      map.set(key,{args,resolves:[resolve],rejects:[rejects]})
                }else{
                    //如果已经有相同的hash了,那么就把resolve存起来
                    map.get(key).resolves.push(resolve)
                    map.get(key).rejects.push(reject)
                }
                if(flag){
                    flag= false
                    setTimeout(()=>{
                         for(let [key,value] of map){
                            let { args, resolves } = value
                            fetch(...args).then(value=>{
                                resolves.forEach(r=>{
                                    r(value)
                                })
                                map.delete(key)     //遍历完就删掉
                            },error=>{reject(error)})
                         }
                        flag = true
                    },time)          
                }
          })
    }
}复制代码

-----------------------------------分割线-----------------------------------------------------

但是实际工作中我们一般都使用主流的axios,所以我们需要把上述的逻辑给放到axios的interceptors里面去,在reuest.interceptors里面,我们必然要把上面的逻辑给放进去,但是碰到

已经有重复参数的请求,可以直接就调用axiso提供的cancel方法,把这个方法给直接取消掉,这样这个借口直接就不会发出去了,然后在时间窗口参数中去把map的resolves都给往下跑

所以第一部分我们先完成axiso.interceptors.request的部分,其实跟上面的代码差不多,

但是要注意没重复的接口给个config.myHash和重复的接口给个cancelReason,

这样的话res那里当做key用

let map = new Map()
let flag = true
let cancel
let DUPLICATE = "duplicate request :"   //这里以冒号分割
function hash(config){
      return JSON.stringify(config)     //这里直接以json来为例,可根据实际情况定
}axios.interceptors.request.use((config)=>{
    return new Promise(resolve=>{
          let key = hash(config)
          config.myHash = key             //这里给到key,这样res的时候可以作为key
          if(map.has(key)){
               map.set(key,{config,resolves:[resolve]})
           }else{
               //如果发现有重复的直接cancel
               config.cancelToken = new axios.CancelToken(c=>{
                   cancel =c
               })
               cancel(DUPLICATE + key)    //这里给一个cancel的信息,reject的时候会拿到,作为key
               map.get(key).resolves.push(resolve)
           }
          if (flag) {            setTimeout(() => {               flag = false               for (let [key, value] of map) {                   let { config, resolves } = value                    resolves.forEach(r => {                       r(config)                       map.delete(key)                    })               }               flag = true            }, 200)         }      })
})复制代码

这样的话我们必然会在response.interceptors中会收到取消掉的信息,但是需要注意的是取消的接口会在请求的接口前面先回来

比如,我们第一个请求是axios.get("a",{params:{a:1}}),第二个请求也是axios.get("a",{params:{a:1}}),这时候发现拦截器发现重复了,但是我们在aixos.interceptors.response里面的拦截器一定会先收到第二个重复请求的reject,

因为其实在axios的源码中的xhr.js如果碰到了config.cancelToken发现有token就直接rejct,请求都不用发,所以肯定是快的,所以我们需要再来一个respMap来存储key,然后等待第一个不重复的请求的数据value回来,再去resolve(value),来实现重复的接口请求也能拿到数据的效果

let resMap = new Map()
axios.interceptors.response.use(resp=>{
    return new Promise(resolve=>{
        let key
        if(key = resp.config.myHash){    //成功了就看有没有我们自己添加的key
            resMap.has(key) && resMap.get(key).resolves.push(resolve)
            resMap.get(key).resolves.forEach(r=>{
                r(resp)
            })
        }else{
            resolve(resp)
        }
    })
},error=>{
    //这里必须要返回一个promise,然后把resolve给放到resMap里面去
    return new Promise(resolve=>{
        if(error instanceof axios.Cancel && error.message.indexOf(DUPLICATE) > -1){            //如果发现error是个CANCEL
            let key = error.message.replace(DUPLICATE,"")
            if(!resMap.has(key)){
                resMap.set(key,{
                    reolves:[resolve],
                    rejects:[rejects]
                })
            }else{
                ...                  //错误暂时省略
            }
        }  
    }) 
})复制代码