天天看點

探索JavaScript中的“假暫停”機制

作者:李遊Leo

了解JavaScript中請求的暫停機制是軟體開發過程中的一個重要知識點。在這篇文章中,我們将會通過深入的讨論和探索來解答這個問題:“JS發起的請求可以暫停嗎?”

探索JavaScript中的“假暫停”機制

首先,我們需要明确這個問題包含的兩個關鍵概念:一是“暫停”的定義是什麼?二是所謂的"JS發起的請求"指的是什麼?

什麼是“暫停”?

"暫停",或者說是“暫時停止”,指的是在一個已經開始但未結束的過程中的臨時停止。這意味着這個過程可以在某個時間點中斷,然後在另一個時間點重新恢複。

JS發起的請求是什麼?

要回答這個問題,我們需要先簡單了解一下TCP/IP網絡模型。網絡模型從上到下分為應用層、傳輸層、網絡層和網絡接口層。在每次網絡傳輸中,應用資料在發送到目标之前,都需要通過網絡模型的每一層進行包裝。這就像寄快遞一樣,我們需要先打包物品、确認包裹的大小,然後将包裹裝進盒子、登記目的地,最後将包裹裝上車,送往目的地。

探索JavaScript中的“假暫停”機制

網絡傳輸示意圖

在這裡,“請求(Request)”可以被了解為用戶端通過多次資料網絡傳輸,将單份資料完整地發送給服務端的行為。而服務端對某次請求發送的回應資料,可以被稱之為“響應(Response)”。

理論上來說,應用層的協定可以通過各種手段,比如标記資料包的序列号,來實作暫停的機制。但是,TCP協定并不支援暫停。TCP協定的資料傳輸是流式的,資料被視為一連串的位元組流。用戶端發送的資料會被拆分成多個TCP段,這些段在網絡中是獨立傳輸的,是以無法直接控制每個TCP段的傳輸,也就無法實作暫停請求或者暫停響應的功能。

探索JavaScript中的“假暫停”機制

解答提問

如果所說的“請求”是指網絡模型中的一次請求傳輸,那麼很明顯,這樣的請求是無法被暫停的。

但是,如果我們從JS發起的請求的角度來看這個問題,那問題中的“請求”,更可能是指JS運作時中發起的XMLHttpRequest或fetch請求。既然請求已經發起,那麼問題自然就變成了“響應是否可以被暫停”。

我們都知道,像大檔案的分片上傳、分片下載下傳等功能,本質上是将分片順序定好之後按順序請求,然後通過中斷順序并記錄中斷點來實作暫停和重傳的機制。然而,對于單個請求來說,并沒有這樣的環境。

用JS實作“假暫停”機制

雖然我們無法真正實作請求的暫停,但我們可以模拟一個“假暫停”的功能。在前端的業務場景中,資料并不是一接收到就可以直接展示給使用者的。前端開發者需要對這些資料進行處理後,才能渲染到界面上。如果我們能在請求發起之前增加一個控制器,在請求傳回時,如果控制器處于暫停狀态,則不處理資料,等到控制器恢複後再進行處理。這樣也能達到我們的目的。接下來,我們會嘗試一下如何實作這樣一個假暫停的功能。

我們可以設計一個控制器Promise,和請求一起被Promise.all包裹起來。當fetch完成時,判斷這個控制器的暫停狀态,如果沒有被暫停,那麼控制器就可以直接resolve,整個Promise.all也随之resolve。

下面是一段具體的代碼實作:

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


function createPauseControllerPromise () {
  const result = {
    isPause: false,
    resolveWhenResume: false,
    resolve (value?: any) {},
    pause () {
      this.isPause = true
    },
    resume () {
      if (!this.isPause) return
      this.isPause = false
      if (this.resolveWhenResume) {
          this.resolve()
      }
    },
    promise: Promise.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: () => void, resume: () => void }
}

           

我們可以通過調用requestWithPauseControl(_request)來替代調用_request,通過傳回的pause和resume方法控制暫停和繼續。

用法

在我們的示例中,我們将模拟一個情景,假設你正在請求一個巨大的JSON檔案,這可能需要一些時間。然後,我們将實作一個按鈕,使用者可以點選它來暫停和恢複請求。

// 創造我們的"大"請求
function bigRequest() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ data: "This is a big JSON file." });
        }, 5000);
    });
}


// 使用我們的暫停控制函數
const controlledRequest = requestWithPauseControl(bigRequest);


// 建立暫停/恢複按鈕
const pauseButton = document.createElement("button");
pauseButton.innerHTML = "Pause/Resume";
pauseButton.addEventListener("click", () => {
    if (controlledRequest.isPaused) {
        controlledRequest.resume();
        console.log("Request resumed");
    } else {
        controlledRequest.pause();
        console.log("Request paused");
    }
});


// 将按鈕添加到頁面
document.body.appendChild(pauseButton);


// 發起請求
controlledRequest.then(data => {
    console.log("Data received: ", data);
}).catch(error => {
    console.error("Error: ", error);
});           

這個案例可以在一個網頁上運作,當使用者點選按鈕時,請求将在暫停和恢複之間切換,最後接收到的資料将列印在控制台中。雖然實際的請求沒有真正暫停(因為我們不能直接暫停一個已經發送的HTTP請求),但我們可以控制當資料傳回時我們做什麼,進而模拟出暫停和恢複的效果。

請注意,我們這裡的bigRequest函數僅用于模拟一個需要較長時間才能完成的請求。在實際應用中,這将是一個實際的網絡請求,例如使用fetch或axios等。

總結

在這篇文章中,我們讨論了JS發起的請求能否被暫停的問題,探讨了暫停的定義和請求的含義,并且介紹了如何在JS中實作假暫停的機制。雖然在網絡層面,我們無法直接控制請求的暫停,但是在應用層面,我們可以通過一些巧妙的設計,實作請求的暫停功能,進而在一定程度上滿足我們的業務需求。

感謝您閱讀本文,如果對您有幫助,請點贊、關注和收藏。您的支援就是我繼續的動力,讓我們一起在前端的道路上不斷前行,共同成長!

繼續閱讀