<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>

          如何處理前端開(kāi)發(fā)中的競(jìng)態(tài)請(qǐng)求

          共 7487字,需瀏覽 15分鐘

           ·

          2023-10-16 02:46


          點(diǎn)擊上方  前端陽(yáng)光 ,關(guān)注公眾號(hào)

          回復(fù) 加群 ,加入技術(shù)交流群交流群

          a42cb68bcdc02a480f7e143fb0b5ea50.webp


          前言

          競(jìng)態(tài)條件(Race Conditions)在前端開(kāi)發(fā)中是一種常見(jiàn)的問(wèn)題,特別是在多個(gè)異步操作同時(shí)競(jìng)爭(zhēng)資源或執(zhí)行時(shí)。這可能導(dǎo)致意外的結(jié)果,如數(shù)據(jù)不一致、重復(fù)請(qǐng)求和UI錯(cuò)誤。

          舉個(gè)??

          一個(gè)最常見(jiàn)的場(chǎng)景就是選項(xiàng)卡切換,用戶(hù)快速切換Tab,由于網(wǎng)絡(luò)延遲等原因,很可能最后停留的Tab所展示的內(nèi)容,并不是用戶(hù)想要看到的內(nèi)容

          df26914ddd0a33641449836cd095f394.webp

          出現(xiàn)這種情況是因?yàn)?,用?hù)最先點(diǎn)擊的Tab請(qǐng)求把最后點(diǎn)擊的Tab請(qǐng)求覆蓋了(由于網(wǎng)絡(luò)原因,最先發(fā)出的請(qǐng)求最后到達(dá)):

          9b22079c9612181ab9134750e618c190.webp

          解決方案

          取消請(qǐng)求

          通常我們可以在新的請(qǐng)求發(fā)起之前,將舊的、未到達(dá)的請(qǐng)求給取消掉,這樣舊的請(qǐng)求就不會(huì)覆蓋新的請(qǐng)求了

          XMLHttpRequest 取消請(qǐng)求

          XMLHttpRequest(XHR)是一個(gè)內(nèi)建的瀏覽器對(duì)象,它允許使用 JavaScript 發(fā)送 HTTP 請(qǐng)求。

          如果請(qǐng)求已被發(fā)出,可以使用 abort() 方法立刻中止請(qǐng)求。

                
                const xhr= new XMLHttpRequest();

          xhr.open('GET''https://xxx');
          xhr.send();
          // 取消請(qǐng)求
          xhr.abort();

          Fetch API 取消請(qǐng)求

          fetch 號(hào)稱(chēng)是 AJAX 的替代品,出現(xiàn)于 ES6,它也可以發(fā)出類(lèi)似 XMLHttpRequest 的網(wǎng)絡(luò)請(qǐng)求。

          主要的區(qū)別在于 fetch 使用了 Promise,要中止 fetch 發(fā)出的請(qǐng)求,需要使用 AbortController。

                
                const controller = new AbortController();
          const signal = controller.signal;

          fetch('/xxx', {
            signal,
          }).then(function(response) {
            //...
          });
          // 取消請(qǐng)求
          controller.abort();

          Axios cancel Token 取消請(qǐng)求

          相比原生 API,大多項(xiàng)目都會(huì)選擇 axios 進(jìn)行請(qǐng)求。

                
                import Axios from 'axios'

          const CancelToken = Axios.CancelToken
          let cancel

          export const getSomeResource = (params: GetSomeResourceReq) => {
            if (cancel) {
              cancel()
            }
            const res = Axios.post('/xxx', params)
            return res.catch((err) => { // 取消了axios請(qǐng)求會(huì)走到異常處理,我們需要對(duì)這種錯(cuò)誤進(jìn)行過(guò)濾
              if (Axios.isCancel(err)) {
                return {} as GetSomeResourceRsp
              }
              throw err
            })
          }

          忽略請(qǐng)求

          相較于取消請(qǐng)求,忽略請(qǐng)求更為通用。我們只需要關(guān)注我們最后一次請(qǐng)求的結(jié)果,如果某次請(qǐng)求的返回結(jié)果并不是最新的,那么我們就忽略掉這個(gè)請(qǐng)求。忽略請(qǐng)求的一個(gè)精髓在于終止Promise的響應(yīng),這也是字節(jié)面試官經(jīng)常問(wèn)到的一個(gè)問(wèn)題,我們需要做的就是返回一個(gè)Pending的Promise,從而做到終止的效果 使用鎖標(biāo)記

          這里的鎖只的是某個(gè)唯一標(biāo)識(shí),通過(guò)這個(gè)標(biāo)識(shí)來(lái)對(duì)比當(dāng)前請(qǐng)求是否過(guò)期,如下文的prevTimestamp

          我們通過(guò)閉包,實(shí)現(xiàn)對(duì)prevTimestamp的緩存,從而做到對(duì)每次請(qǐng)求的返回進(jìn)行對(duì)比的效果

                
                
          /**
           * 處理競(jìng)態(tài)請(qǐng)求
           *
           * @export
           * @param {(...params: any[]) => Promise<any>} fn
           * @return {*}
           */
          export default function useFetch<T, P>(fn: (...params: [P, ...any[]]) => Promise<T>) {
            let prevTimestamp
            return function (params: P, ...rest: any[]): Promise<T> {
              return new Promise((resolve, reject) => {
                const curTimestamp = prevTimestamp = Date.now()
                const context = this
                fn.call(context, params, ...rest)
                  .then(res => {
                    // 只處理最新請(qǐng)求的返回
                    if (curTimestamp === prevTimestamp) {
                      resolve(res)
                    }
                  }).catch(err => {
                    // 只處理最后一次請(qǐng)求的異常
                    if (curTimestamp === prevTimestamp) {
                      reject(err)
                    }
                  })
              })
            }
          }

          在使用的時(shí)候,我們就可以用這個(gè)方法把真正要發(fā)出的請(qǐng)求包一下(針對(duì)某一個(gè)具體的原子請(qǐng)求,從而做到多請(qǐng)求皆可竟態(tài)處理)

                
                import Axios from 'axios'

          const _getSomeResource = (params: GetSomeResourceReq) => {
            return Axios.post('/xxx', params)
          }

          export const getSomeResource = useFetch(_getSomeResource)

          使用隊(duì)列

          與使用鎖類(lèi)似,我們把每個(gè)請(qǐng)求都放進(jìn)隊(duì)列里,對(duì)每次返回的請(qǐng)求進(jìn)行判斷,如果這個(gè)請(qǐng)求不是最新的請(qǐng)求,那么就忽略掉

                
                
          /**
           * 處理競(jìng)態(tài)請(qǐng)求
           *
           * @export
           * @param {(...params: any[]) => Promise<any>} fn
           * @return {*}
           */
          export default function useFetch<T, P>(fn: (...params: [P, ...any[]]) => Promise<T>) {
            const queue = []
            return function (params: P, ...rest: any[]): Promise<T> {
              const p = new Promise((resolve, reject) => {
                const context = this
                fn.call(context, params, ...rest)
                  .then(res => {
                    const isLatest = queue[queue.length - 1] === p
                    // 只處理最新請(qǐng)求的返回
                    if (isLatest && !p.expired) {
                      resolve(res)
                    } else {
                        p.expired = true // 避免后面的請(qǐng)求出列后,老的請(qǐng)求繼續(xù)執(zhí)行
                    }
                  }).catch(err => {
                    const isLatest = queue[queue.length - 1] === p
                    // 只處理最后一次請(qǐng)求的異常
                    if (isLatest && !p.expired) {
                      reject(err)
                    } else {
                      p.expired = true // 避免后面的請(qǐng)求出列后,老的請(qǐng)求繼續(xù)執(zhí)行
                    }
                  }).finally(() => {
                     const idx = queue.findIndex(item => item === p)
                     queue.splice(idx, 1)
                  })
              })
              queue.push(p)
              return p
            }
          }

          總結(jié)

          為了解決前端開(kāi)發(fā)中遇到的竟態(tài)請(qǐng)求問(wèn)題,我們提供了兩種解決方案:取消請(qǐng)求 & 忽略請(qǐng)求

          這兩種方案都有一定的優(yōu)劣,取消請(qǐng)求會(huì)導(dǎo)致客戶(hù)端主動(dòng)斷開(kāi)連接,可能對(duì)后臺(tái)異常監(jiān)控帶來(lái)影響;忽略請(qǐng)求可能導(dǎo)致前端請(qǐng)求過(guò)于頻繁,增加后臺(tái)服務(wù)器壓力,可以結(jié)合截流/防抖機(jī)制加以?xún)?yōu)化

          作者:WeilinerL 鏈接:https://juejin.cn/post/7280740005567332404


          a8f98047c94c59e4c67f7f9b9a75a4cd.webp

          往期推薦


          優(yōu)秀文章匯總:https://github.com/Sunny-lucking/blog
          技術(shù)交流群


          我組建了技術(shù)交流群,里面有很多 大佬,歡迎進(jìn)來(lái)交流、學(xué)習(xí)、共建?;貜?fù) 加群 即可。 后臺(tái)回復(fù)「 電子書(shū) 」即可免費(fèi)獲取 27本 精選的前端電子書(shū)!



              “分享、點(diǎn)贊 、 在看” 支持一波??


          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  国产高清在线激情 | 无码人妻精品一区二区蜜桃色欲 | 亚洲第一A片 | 国产激情有吗在线观看 | 亚洲蜜桃一区二区 |