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

          【JS】1792- 前端中 JS 發(fā)起的請求可以暫停嗎?

          共 6040字,需瀏覽 13分鐘

           ·

          2023-09-06 17:44

          之前在沸點看到一個哥們提出一個問題。

          image.png

          這個問題非常有意思,我一看到就想了很多可以回復(fù)的答案,但是評論區(qū)太窄,就直接開一篇文章來寫了。

          審題

          JS 發(fā)起的請求可以暫停嗎?這一句話當(dāng)中有兩個概念需要明確,一是什么樣的狀態(tài)才能稱之為 暫停?二是 JS 發(fā)起的請求 是什么?

          怎么樣才算暫停?

          暫停 全稱暫時停止,在已開始未結(jié)束的過程中臨時停止可以稱之為暫停,意味著這個過程可以在某個時間點截斷然后在另一個時間點重新續(xù)上。

          請求應(yīng)該是什么?

          這里得先介紹一下 TCP/IP 網(wǎng)絡(luò)模型, 網(wǎng)絡(luò)模型自上而下分為 應(yīng)用層、傳輸層、網(wǎng)絡(luò)層和網(wǎng)絡(luò)接口層。

          image.png

          上圖表示的意思是,每次網(wǎng)絡(luò)傳輸,應(yīng)用數(shù)據(jù)在發(fā)送至目標(biāo)前都需要通過網(wǎng)絡(luò)模型一層一層的包裝,就像寄快遞一樣,把要寄的物品先打包好登記一下大小,再裝在盒子里登記一下目的地,然后再裝到車上,最后送往目的地。

          請求(Request) 這個概念就可以理解為客戶端通過若干次數(shù)據(jù)網(wǎng)絡(luò)傳輸,將單份數(shù)據(jù)完整發(fā)給服務(wù)端的行為,而針對某次請求服務(wù)端往客戶端發(fā)送的答復(fù)數(shù)據(jù)則可以稱之為 響應(yīng)(Response)

          理論上應(yīng)用層的協(xié)議可以通過類似于標(biāo)記數(shù)據(jù)包序列號等等一系列手段來實現(xiàn)暫停機制。但是 TCP 協(xié)議并不支持,TCP 協(xié)議的數(shù)據(jù)傳輸是流式的,數(shù)據(jù)被視為一連串的字節(jié)流。客戶端發(fā)送的數(shù)據(jù)會被拆分成多個 TCP 段(TCP segments),而這些段在網(wǎng)絡(luò)中是獨立傳輸?shù)模瑹o法直接控制每個 TCP 段的傳輸,因此也無法實現(xiàn)暫停請求或者暫停響應(yīng)的功能。

          image.png

          解答提問

          如果請求是指網(wǎng)絡(luò)模型中的一次請求傳輸,那理所當(dāng)然是不可能暫停的。

          來看看提問者的使用場景 —— JS 發(fā)起的請求,那么可以認(rèn)為問題當(dāng)中的請求,應(yīng)該是指在 JS 運行時中發(fā)起的 XMLHttpRequest 或者是 fetch 請求,而請求既然已經(jīng)發(fā)起,那問的自然就是 響應(yīng)是否能夠被暫停

          我們都知道像大文件分片上傳、以及分片下載之類的功能本質(zhì)上是將分片順序定好之后按順序請求,然后就可以通過中斷順序并記錄中斷點來實現(xiàn)暫停重傳的機制,而單個請求并不具備這樣的環(huán)境。

          用 JS 實現(xiàn) ”假暫停” 機制

          雖然不能真正意義上實現(xiàn)暫停請求,但是我們其實可以模擬一個 假暫停 的功能,在前端的業(yè)務(wù)場景上,數(shù)據(jù)不是收到就可以直接打在客戶臉上的(什么光速打擊),前端開發(fā)者需要對這些數(shù)據(jù)進行處理之后渲染在界面上,如果我們能在請求發(fā)起之前增加一個控制器,在請求回來時,如果控制器為暫停狀態(tài)則不處理數(shù)據(jù),等待控制器恢復(fù)后再進行處理,是不是也能到達到目的?讓我們試著實現(xiàn)一下。

          假如我們使用 fetch 來請求。我們可以設(shè)計一個控制器 Promise 和請求放在一起用 Promise.all 包裹,當(dāng) fetch 完成時判斷這個控制器的暫停狀態(tài),如果沒有被暫停,則控制器也直接 resolve,同時整個 Promise.all 也 resolve 拋出。

          function _request ({
            return new Promise<number>((res) => setTimeout(() => {
              res(123)
            }, 3000))
          }

          // 原本想使用 class extends Promise 來實現(xiàn)
          // 結(jié)果一直出現(xiàn)這個問題 https://github.com/nodejs/node/issues/13678
          function createPauseControllerPromise ({
            const result = {
              isPausefalse,
              resolveWhenResumefalse,
              resolve (value?: any) {},
              pause () {
                this.isPause = true
              },
              resume () {
                if (!this.isPause) return
                this.isPause = false
                if (this.resolveWhenResume) {
                    this.resolve()
                }
              },
              promisePromise.resolve()
            }
            const promise = new Promise<void>((res) => {
              result.resolve = res
            })
            result.promise = promise

            return result
          }

          function requestWithPauseControl <T extends () => Promise<any>>(request: T{
            const controller = createPauseControllerPromise()
            
            const controlRequest = request().then((data) => {
                if (!controller.isPause) controller.resolve()
                return data
            }).finally(() => {
                controller.resolveWhenResume = true
            })
            
            const result = Promise.all([controlRequest, controller.promise]).then(data => {
                controller.resolve()
                return data[0]
            });
            
            (result as any).pause = controller.pause.bind(controller);
            (result as any).resume = controller.resume.bind(controller);
            
            return result as ReturnType<T> & { pause() => voidresume() => void }
          }

          用法

          我們可以通過調(diào)用 requestWithPauseControl(_request) 來替代調(diào)用 _request 使用,通過返回的 pauseresume 方法控制暫停和繼續(xù)。

          const result = requestWithPauseControl(_request).then((data) => {
              console.log(data)
          })

          if (Math.random() > 0.5) { result.pause() }

          setTimeout(() => {
              result.resume()
          }, 4000)

          補充

          有些同學(xué)錯誤的認(rèn)為網(wǎng)絡(luò)請求和響應(yīng)是絕對不可以暫停的,我特意在文章前面提到了有關(guān)數(shù)據(jù)傳輸?shù)膬?nèi)容,并且掛了一句“理論上應(yīng)用層的協(xié)議可以通過類似于標(biāo)記數(shù)據(jù)包序列號等等一系列手段來實現(xiàn)暫停機制”,這句話的意思是,如果你魔改 HTTP 或者自己設(shè)計實現(xiàn)一個應(yīng)用層協(xié)議(例如像 socket、vmess 這些協(xié)議),只要雙端支持該協(xié)議,是可以實現(xiàn)請求暫停或者響應(yīng)暫停的,而且這不會影響到 TCP 連接,但是實現(xiàn)暫停機制需要對各種場景和 TCP 策略兜底才能有較好的可靠性。

          例如,提供一類控制報文用于控制傳輸暫停,首先需要對所有數(shù)據(jù)包的序列號標(biāo)記順序,當(dāng)需要暫停時,發(fā)送該序列號的暫停報文給接收端,接收端收到暫停報文就將已接收數(shù)據(jù)包的塊標(biāo)記返回給發(fā)送端等等(這和分片上傳機制一樣)。

          最后

          以上就是本篇文章分享的全部內(nèi)容了。

          這里是 Xekin(/zi:kin/)。喜歡的掘友們可以點贊關(guān)注點個收藏~

          最近摸魚時間比較多,寫了一些奇奇怪怪有用但又不是特別有用的工具,不過還是非常有意思的,之后會一一寫文章分享出來,感謝各位支持。

          關(guān)于本文

          作者:xekin

          https://juejin.cn/post/7260742402397863992


          往期回顧

          #

          如何使用 TypeScript 開發(fā) React 函數(shù)式組件?

          #

          11 個需要避免的 React 錯誤用法

          #

          6 個 Vue3 開發(fā)必備的 VSCode 插件

          #

          3 款非常實用的 Node.js 版本管理工具

          #

          6 個你必須明白 Vue3 的 ref 和 reactive 問題

          #

          6 個意想不到的 JavaScript 問題

          #

          試著換個角度理解低代碼平臺設(shè)計的本質(zhì)

          回復(fù)“加群”,一起學(xué)習(xí)進步

          瀏覽 348
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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片 | 亚洲 A V中文字幕 | 天天撸视频 夜夜撸视频 |