多个相同组件重复请求?

2,858 阅读3分钟

问题引入

在页面开发的时候, 如果一个页面中有多个相同的组件, 那么该组件每次onMounted的时候都会去调用对应的api,就会造成类似这样的后果多次请求了同一个api

image.png

如何解决?

方式一

借助一个大佬的思路使用单例模式

基本思路

  • 有缓存拿缓存
  • 没缓存判断如果是第 1 个请求的,就去请求远端
  • 如果不是第 1 个请求的,就等
let cache = null;
let count = 0;

async function delay(ms = 200) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

export async function getSignature() {
    if (cache) { return cache; }

    if (count++) {
        // 如果有计数说明自己不是第 1 个,就等。注意这里判断的是加之前的 count
        // 循环里最好再加个超时判断
        while (!cache) { await delay(); }
    } else {
        // 是第 1 个就去请求
        // 如果这里有可能会抛异常,抛异常也不要漏了 count--
        // (这个示例代码没做容错,自己加)
        cache = await fetchSignature();
    }

    count--;    // 记得减回去,方便以后如果要刷新 cache 的时候用
    return cache;
}

多个组件调用 getSignature() , 如果是第一次请求,走的是 if(count++) ,此时if判断语句里面的 count 值还是 0 , 走出if 语句之后才是 1 , 所以会进入 while(!cache) 语句, 进行循环等待, 到了第二个组件请求的时候, count 的值 已经是 1 了,那么这个时候 走的就是else 语句, 就可以获得cache结果,此时 第一次请求的条件 !cache也不成立了, 就会退出循环等待, 就会返回 cache的请求结果

关于 count-- : 由于首次和后续的29次请求的时候都会执行count++ 这个语句, 那么每次的count 结果都会加1 . 结束后都需要把count减一次,不需要放在else中。这样下来30个请求结束后,count置为0了。如果放在else中,那么30次请求结束后,count的值应该是1.

方式二

同样使用的是单例模式, 不过这里是异步单例模式 关于异步单例模式,我觉得可以参考这篇文章高级异步模式 - Promise 单例

image.png

实践一波

 function getSomething() {
     return new Promise(resolve => {
         const result = fetch('http://hn.algolia.com/api/v1/search?query=vue').then(val => resolve(val.json()))
     })
 }

 let myfetch = null
 async function getData() {
     if (myfetch) return myfetch
     myfetch = getSomething()
     console.log(345);
     try {
         const result = await myfetch   // 这里一定要 await myfetch
         console.log(456);
         myfetch = null
         return result
     } catch (err) {
         console.err('err')
     }
 }

 const result1 = getData()
 const result2 = getData()
 const result3 = getData()
 const result4 = getData()

 console.log(123);
 console.log(result1, 'result1');        // Promise {<pending>} 'result1'
 console.log(result2, 'result2');        // Promise {<pending>} 'result2'
 console.log(result3, 'result3');        // Promise {<pending>} 'result3'
 console.log(result4, 'result4');        // Promise {<pending>} 'result4'
 setTimeout(() => {
     console.log(result1, 'result1');     // Promise {<fulfilled>: {…}} 'result1'
     console.log(result2, 'result2');     // Promise {<fulfilled>: {…}} 'result2'
     console.log(result3, 'result3');     // Promise {<fulfilled>: {…}} 'result3'
     console.log(result4, 'result4');     // Promise {<fulfilled>: {…}} 'result4'
 }, 1500)

第一次调用的时候, 没有myfetch , 就去 调用 接口, 此时的 myfetch 是pending 状态,因为是多个组件同时调用getData()函数, 但是我们再第一次的时候已经给myfetch赋值了,所以调用getData()函数返回的都是pending 状态的myfetch , 等到第一个请求的结果拿到了, 那么其他组件也就拿到了这个结果,所以就不用每个组件都去走一次http请求拿到同样的结果,这样下来就可以不用每个组件都去调用接口

image.png