了解JavaScript中請求的暫停機制是軟體開發過程中的一個重要知識點。在這篇文章中,我們将會通過深入的讨論和探索來解答這個問題:“JS發起的請求可以暫停嗎?”
首先,我們需要明确這個問題包含的兩個關鍵概念:一是“暫停”的定義是什麼?二是所謂的"JS發起的請求"指的是什麼?
什麼是“暫停”?
"暫停",或者說是“暫時停止”,指的是在一個已經開始但未結束的過程中的臨時停止。這意味着這個過程可以在某個時間點中斷,然後在另一個時間點重新恢複。
JS發起的請求是什麼?
要回答這個問題,我們需要先簡單了解一下TCP/IP網絡模型。網絡模型從上到下分為應用層、傳輸層、網絡層和網絡接口層。在每次網絡傳輸中,應用資料在發送到目标之前,都需要通過網絡模型的每一層進行包裝。這就像寄快遞一樣,我們需要先打包物品、确認包裹的大小,然後将包裹裝進盒子、登記目的地,最後将包裹裝上車,送往目的地。
網絡傳輸示意圖
在這裡,“請求(Request)”可以被了解為用戶端通過多次資料網絡傳輸,将單份資料完整地發送給服務端的行為。而服務端對某次請求發送的回應資料,可以被稱之為“響應(Response)”。
理論上來說,應用層的協定可以通過各種手段,比如标記資料包的序列号,來實作暫停的機制。但是,TCP協定并不支援暫停。TCP協定的資料傳輸是流式的,資料被視為一連串的位元組流。用戶端發送的資料會被拆分成多個TCP段,這些段在網絡中是獨立傳輸的,是以無法直接控制每個TCP段的傳輸,也就無法實作暫停請求或者暫停響應的功能。
解答提問
如果所說的“請求”是指網絡模型中的一次請求傳輸,那麼很明顯,這樣的請求是無法被暫停的。
但是,如果我們從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中實作假暫停的機制。雖然在網絡層面,我們無法直接控制請求的暫停,但是在應用層面,我們可以通過一些巧妙的設計,實作請求的暫停功能,進而在一定程度上滿足我們的業務需求。
感謝您閱讀本文,如果對您有幫助,請點贊、關注和收藏。您的支援就是我繼續的動力,讓我們一起在前端的道路上不斷前行,共同成長!