<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何設計一個支持并發(fā)的前端緩存接口?

          共 10283字,需瀏覽 21分鐘

           ·

          2023-05-29 16:13

          b8aa2e890ec0aec7e183c9fd5bb8a285.webp

          緩存池

          緩存池不過就是一個map,存儲接口數(shù)據(jù)的地方,將接口的路徑和參數(shù)拼到一塊作為key,數(shù)據(jù)作為value存起來罷了,這個咱誰都會。

          const?cacheMap?=?new?Map();

          封裝一下調(diào)用接口的方法,調(diào)用時先走咱們緩存數(shù)據(jù)。

          import?axios,?{?AxiosRequestConfig?}?from?'axios'

          //?先來一個簡簡單單的發(fā)送
          export?function?sendRequest(request:?AxiosRequestConfig)?{
          ??return?axios(request)
          }

          然后加上咱們的緩存

          import?axios,?{?AxiosRequestConfig?}?from?'axios'
          import?qs?from?'qs'

          const?cacheMap?=?new?Map()

          interface?MyRequestConfig?extends?AxiosRequestConfig?{
          ??needCache?:?boolean
          }

          //?這里用params是因為params是?GET?方式穿的參數(shù),我們的緩存一般都是?GET?接口用的
          function?generateCacheKey(config:?MyRequestConfig)?{
          ??return?config.url?+?'?'?+?qs.stringify(config.params)
          }

          export?function?sendRequest(request:?MyRequestConfig)?{
          ??const?cacheKey?=?generateCacheKey(request)
          ??//?判斷是否需要緩存,并且緩存池中有值時,返回緩存池中的值
          ??if?(request.needCache?&&?cacheMap.has(cacheKey))?{
          ????return?Promise.resolve(cacheMap.get(cacheKey))
          ??}
          ??return?axios(request).then((res)?=>?{
          ????//?這里簡單判斷一下,200就算成功了,不管里面的data的code啥的了
          ????if?(res.status?===?200)?{
          ??????cacheMap.set(cacheKey,?res.data)
          ????}
          ????return?res
          ??})
          }

          然后調(diào)用

          const?getArticleList?=?(params:?any)?=>
          ??sendRequest({
          ????needCache:?true,
          ????url:?'/article/list',
          ????method:?'get',
          ????params
          ??})

          getArticleList({
          ??page:?1,
          ??pageSize:?10
          }).then((res)?=>?{
          ??console.log(res)
          })

          這個部分就很簡單,我們在調(diào)接口時給一個needCache的標記,然后調(diào)完接口如果成功的話,就會將數(shù)據(jù)放到cacheMap中去,下次再調(diào)用的話,就直接返回緩存中的數(shù)據(jù)。

          并發(fā)緩存

          上面的雖然看似實現(xiàn)了緩存,不管我們調(diào)用幾次,都只會發(fā)送一次請求,剩下的都會走緩存。但是真的是這樣嗎?

          getArticleList({
          ??page:?1,
          ??pageSize:?10
          }).then((res)?=>?{
          ??console.log(res)
          })
          getArticleList({
          ??page:?1,
          ??pageSize:?10
          }).then((res)?=>?{
          ??console.log(res)
          })

          其實這樣,就可以測出,我們的雖然設計了緩存,但是請求還是發(fā)送了兩次,這是因為我們第二次請求發(fā)出時,第一次請求還沒完成,也就沒給緩存池里放數(shù)據(jù),所以第二次請求沒命中緩存,也就又發(fā)了一次。

          問題

          那么,有沒有一種辦法讓第二次請求等待第一次請求調(diào)用完成,然后再一塊返回呢?

          思考

          有了!我們寫個定時器就好了呀,比如我們可以給第二次請求加個定時器,定時器時間到了再去cacheMap中查一遍有沒有緩存數(shù)據(jù),沒有的話可能是第一個請求還沒好,再等幾秒試試!

          可是這樣的話,第一個請求的時候也會在原地等呀!??

          那這樣的話,讓第一個請求在一個地方貼個告示不就好了,就像上廁所的時候在門口掛個牌子一樣!??

          //?存儲緩存當前狀態(tài),相當于掛牌子的地方
          const?statusMap?=?new?Map<string,?'pending'?|?'complete'>();

          export?function?sendRequest(request:?MyRequestConfig)?{
          ??const?cacheKey?=?generateCacheKey(request)

          ??//?判斷是否需要緩存
          ??if?(request.needCache)?{
          ????if?(statusMap.has(cacheKey))?{
          ??????const?currentStatus?=?statusMap.get(cacheKey)

          ??????//?判斷當前的接口緩存狀態(tài),如果是?complete?,則代表緩存完成
          ??????if?(currentStatus?===?'complete')?{
          ????????return?Promise.resolve(cacheMap.get(cacheKey))
          ??????}

          ??????//?如果是?pending?,則代表正在請求中,這里就等個三秒,然后再來一次看看情況
          ??????if?(currentStatus?===?'pending')?{
          ????????return?new?Promise((resolve,?reject)?=>?{
          ??????????setTimeout(()?=>?{
          ????????????sendRequest(request).then(resolve,?reject)
          ??????????},?3000)
          ????????})
          ??????}
          ????}

          ????statusMap.set(cacheKey,?'pending')
          ??}

          ??return?axios(request).then((res)?=>?{
          ????//?這里簡單判斷一下,200就算成功了,不管里面的data的code啥的了
          ????if?(res.status?===?200)?{
          ??????statusMap.set(cacheKey,?'complete')
          ??????cacheMap.set(cacheKey,?res)
          ????}
          ????return?res
          ??})
          }

          試試效果

          getArticleList({
          ????page:?1,
          ????pageSize:?10
          }).then((res)?=>?{
          ????console.log(res)
          })
          getArticleList({
          ????page:?1,
          ????pageSize:?10
          }).then((res)?=>?{
          ????console.log(res)
          })

          e29a9dad93ddc2f1c3716a0177db715a.webpimage.pngc03b481a90d646a0621408ae575a1d14.webpimage.png

          成了!這里真的做到了,可以看到我們這里打印了兩次,但是只發(fā)了一次請求。

          優(yōu)化??

          可是用setTimeout等待還是不太優(yōu)雅,如果第一個請求能在3s以內(nèi)完成還行,用戶等待的時間還不算太久,還能忍受??扇绻?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(239,112,96);">3.1s的話,第二個接口用戶可就白白等了6s之久,那么,有沒有一種辦法,能讓第一個接口完成后,接著就通知第二個接口返回數(shù)據(jù)呢?

          等待,通知,這種場景我們寫代碼用的最多的就是回調(diào)了,但是這次用的是promise啊,而且還是毫不相干的兩個promise。等等!callbackpromise,promise本身就是callback實現(xiàn)的!promisethen會在resole被調(diào)用時調(diào)用,這樣的話,我們可以將第二個請求的resole放在一個callback里,然后在第一個請求完成的時候,調(diào)用這個callback!??

          //?定義一下回調(diào)的格式
          interface?RequestCallback?{
          ??onSuccess:?(data:?any)?=>?void
          ??onError:?(error:?any)?=>?void
          }

          //?存放等待狀態(tài)的請求回調(diào)
          const?callbackMap?=?new?Map<string,?RequestCallback[]>()

          export?function?sendRequest(request:?MyRequestConfig)?{
          ??const?cacheKey?=?generateCacheKey(request)

          ??//?判斷是否需要緩存
          ??if?(request.needCache)?{
          ????if?(statusMap.has(cacheKey))?{
          ??????const?currentStatus?=?statusMap.get(cacheKey)

          ??????//?判斷當前的接口緩存狀態(tài),如果是?complete?,則代表緩存完成
          ??????if?(currentStatus?===?'complete')?{
          ????????return?Promise.resolve(cacheMap.get(cacheKey))
          ??????}

          ??????//?如果是?pending?,則代表正在請求中,這里放入回調(diào)函數(shù)
          ??????if?(currentStatus?===?'pending')?{
          ????????return?new?Promise((resolve,?reject)?=>?{
          ??????????if?(callbackMap.has(cacheKey))?{
          ????????????callbackMap.get(cacheKey)!.push({
          ??????????????onSuccess:?resolve,
          ??????????????onError:?reject
          ????????????})
          ??????????}?else?{
          ????????????callbackMap.set(cacheKey,?[
          ??????????????{
          ????????????????onSuccess:?resolve,
          ????????????????onError:?reject
          ??????????????}
          ????????????])
          ??????????}
          ????????})
          ??????}
          ????}

          ????statusMap.set(cacheKey,?'pending')
          ??}

          ??return?axios(request).then(
          ????(res)?=>?{
          ??????//?這里簡單判斷一下,200就算成功了,不管里面的data的code啥的了
          ??????if?(res.status?===?200)?{
          ????????statusMap.set(cacheKey,?'complete')
          ????????cacheMap.set(cacheKey,?res)
          ??????}?else?{
          ????????//?不成功的情況下刪掉?statusMap?中的狀態(tài),能讓下次請求重新請求
          ????????statusMap.delete(cacheKey)
          ??????}
          ??????//?這里觸發(fā)resolve的回調(diào)函數(shù)
          ??????if?(callbackMap.has(cacheKey))?{
          ????????callbackMap.get(cacheKey)!.forEach((callback)?=>?{
          ??????????callback.onSuccess(res)
          ????????})
          ????????//?調(diào)用完成之后清掉,用不到了
          ????????callbackMap.delete(cacheKey)
          ??????}
          ??????return?res
          ????},
          ????(error)?=>?{
          ??????//?不成功的情況下刪掉?statusMap?中的狀態(tài),能讓下次請求重新請求
          ??????statusMap.delete(cacheKey)
          ??????//?這里觸發(fā)reject的回調(diào)函數(shù)
          ??????if?(callbackMap.has(cacheKey))?{
          ????????callbackMap.get(cacheKey)!.forEach((callback)?=>?{
          ??????????callback.onError(error)
          ????????})
          ????????//?調(diào)用完成之后也清掉
          ????????callbackMap.delete(cacheKey)
          ??????}
          ??????//?這里要返回?Promise.reject(error),才能被catch捕捉到
          ??????return?Promise.reject(error)
          ????}
          ??)
          }

          在判斷到當前請求狀態(tài)是pending時,將promiseresolereject放入回調(diào)隊列中,等待被觸發(fā)調(diào)用。然后在請求完成時,觸發(fā)對應的請求隊列。

          試一下

          getArticleList({
          ????page:?1,
          ????pageSize:?10
          }).then((res)?=>?{
          ????console.log(res)
          })
          getArticleList({
          ????page:?1,
          ????pageSize:?10
          }).then((res)?=>?{
          ????console.log(res)
          })

          d8bb8807aaff2d905630165dc6669c20.webpimage.pngea3f017ecc952e9eb83462381acea4f5.webpimage.png

          OK,完成了。

          再試一下失敗的時候

          getArticleList({
          ????page:?1,
          ????pageSize:?10
          }).then(
          ????(res)?=>?{
          ??????console.log(res)
          ????},
          ????(error)?=>?{
          ??????console.error(error)
          ????}
          )
          getArticleList({
          ????page:?1,
          ????pageSize:?10
          }).then(
          ????(res)?=>?{
          ??????console.log(res)
          ????},
          ????(error)?=>?{
          ??????console.error(error)
          ????}
          )

          c82c5615466c93101a5c0027a41afc08.webpimage.png

          OK,兩個都失敗了。(但是這里的error2早于error1打印,你知道是啥原因嗎???)

          總結(jié)

          promise封裝并發(fā)緩存到這里就結(jié)束啦,不過看到這里你可能會覺著沒啥用處,但是其實這也是我碰到的一個需求才延申出來的,當時的場景是一個頁面里有好幾個下拉選擇框,選項都是接口提供的常量。但是只接口提供了一個接口返回這些常量,前端拿到以后自己再根據(jù)類型挑出來,所以這種情況我們肯定不能每個下拉框都去調(diào)一次接口,只能是寄托緩存機制了。

          這種寫法,在另一種場景下也很好用,比如將需要用戶操作的流程封裝成promise。例如,A頁面點擊A按鈕,出現(xiàn)一個B彈窗,彈窗里有B按鈕,用戶點擊B按鈕之后關閉彈窗,再彈出C彈窗C按鈕,點擊C之后流程完成,這種情況就很適合將每個彈窗里的操作流程都封裝成一個promise,最外面的A頁面只需要連著調(diào)用這幾個promise就可以了,而不需要維護控制這幾個彈窗顯示隱藏的變量了。

          放一下全部代碼

          import?axios,?{?AxiosRequestConfig?}?from?'axios'
          import?qs?from?'qs'

          //?存儲緩存數(shù)據(jù)
          const?cacheMap?=?new?Map()

          //?存儲緩存當前狀態(tài)
          const?statusMap?=?new?Map<string,?'pending'?|?'complete'>()

          //?定義一下回調(diào)的格式
          interface?RequestCallback?{
          ??onSuccess:?(data:?any)?=>?void
          ??onError:?(error:?any)?=>?void
          }

          //?存放等待狀態(tài)的請求回調(diào)
          const?callbackMap?=?new?Map<string,?RequestCallback[]>()

          interface?MyRequestConfig?extends?AxiosRequestConfig?{
          ??needCache?:?boolean
          }

          //?這里用params是因為params是?GET?方式穿的參數(shù),我們的緩存一般都是?GET?接口用的
          function?generateCacheKey(config:?MyRequestConfig)?{
          ??return?config.url?+?'?'?+?qs.stringify(config.params)
          }

          export?function?sendRequest(request:?MyRequestConfig)?{
          ??const?cacheKey?=?generateCacheKey(request)

          ??//?判斷是否需要緩存
          ??if?(request.needCache)?{
          ????if?(statusMap.has(cacheKey))?{
          ??????const?currentStatus?=?statusMap.get(cacheKey)

          ??????//?判斷當前的接口緩存狀態(tài),如果是?complete?,則代表緩存完成
          ??????if?(currentStatus?===?'complete')?{
          ????????return?Promise.resolve(cacheMap.get(cacheKey))
          ??????}

          ??????//?如果是?pending?,則代表正在請求中,這里放入回調(diào)函數(shù)
          ??????if?(currentStatus?===?'pending')?{
          ????????return?new?Promise((resolve,?reject)?=>?{
          ??????????if?(callbackMap.has(cacheKey))?{
          ????????????callbackMap.get(cacheKey)!.push({
          ??????????????onSuccess:?resolve,
          ??????????????onError:?reject
          ????????????})
          ??????????}?else?{
          ????????????callbackMap.set(cacheKey,?[
          ??????????????{
          ????????????????onSuccess:?resolve,
          ????????????????onError:?reject
          ??????????????}
          ????????????])
          ??????????}
          ????????})
          ??????}
          ????}

          ????statusMap.set(cacheKey,?'pending')
          ??}

          ??return?axios(request).then(
          ????(res)?=>?{
          ??????//?這里簡單判斷一下,200就算成功了,不管里面的data的code啥的了
          ??????if?(res.status?===?200)?{
          ????????statusMap.set(cacheKey,?'complete')
          ????????cacheMap.set(cacheKey,?res)
          ??????}?else?{
          ????????//?不成功的情況下刪掉?statusMap?中的狀態(tài),能讓下次請求重新請求
          ????????statusMap.delete(cacheKey)
          ??????}
          ??????//?這里觸發(fā)resolve的回調(diào)函數(shù)
          ??????if?(callbackMap.has(cacheKey))?{
          ????????callbackMap.get(cacheKey)!.forEach((callback)?=>?{
          ??????????callback.onSuccess(res)
          ????????})
          ????????//?調(diào)用完成之后清掉,用不到了
          ????????callbackMap.delete(cacheKey)
          ??????}
          ??????return?res
          ????},
          ????(error)?=>?{
          ??????//?不成功的情況下刪掉?statusMap?中的狀態(tài),能讓下次請求重新請求
          ??????statusMap.delete(cacheKey)
          ??????//?這里觸發(fā)reject的回調(diào)函數(shù)
          ??????if?(callbackMap.has(cacheKey))?{
          ????????callbackMap.get(cacheKey)!.forEach((callback)?=>?{
          ??????????callback.onError(error)
          ????????})
          ????????//?調(diào)用完成之后也清掉
          ????????callbackMap.delete(cacheKey)
          ??????}
          ??????return?Promise.reject(error)
          ????}
          ??)
          }

          const?getArticleList?=?(params:?any)?=>
          ??sendRequest({
          ????needCache:?true,
          ????baseURL:?'http://localhost:8088',
          ????url:?'/article/blogList',
          ????method:?'get',
          ????params
          ??})

          export?function?testApi()?{
          ??getArticleList({
          ????page:?1,
          ????pageSize:?10
          ??}).then(
          ????(res)?=>?{
          ??????console.log(res)
          ????},
          ????(error)?=>?{
          ??????console.error('error1:',?error)
          ????}
          ??)
          ??getArticleList({
          ????page:?1,
          ????pageSize:?10
          ??}).then(
          ????(res)?=>?{
          ??????console.log(res)
          ????},
          ????(error)?=>?{
          ??????console.error('error2:',?error)
          ????}
          ??)
          }

          最后

          對請求結(jié)果是否成功那里處理的比較簡陋,項目里用到的話根據(jù)自己情況來。

          關于本文

          作者:背對疾風https://juejin.cn/post/7104635370796482567
          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品国产污污污免费入口15 | 操逼视频色版 | 俺也操| 俺去了官网 | 五月天激情啪啪网 |