自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何在用戶離開(kāi)頁(yè)面時(shí)可靠地發(fā)送 HTTP 請(qǐng)求

網(wǎng)絡(luò) 通信技術(shù)
您可能希望該請(qǐng)求的分派是同步的,之后我們將繼續(xù)導(dǎo)航離開(kāi)頁(yè)面,而其他服務(wù)器成功地處理該請(qǐng)求。但事實(shí)證明,情況并非總是如此。

有幾次,當(dāng)用戶執(zhí)行導(dǎo)航到不同頁(yè)面或提交表單等操作時(shí),我需要發(fā)送帶有一些數(shù)據(jù)的 HTTP 請(qǐng)求以進(jìn)行記錄??紤]這個(gè)在點(diǎn)擊鏈接時(shí)向外部服務(wù)發(fā)送一些信息的人為示例:

<a href="/some-other-page" id="link">Go to Page</a>

<script>
document.getElementById('link').addEventListener('click', (e) => {
fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: "data"
})
});
});
</script>

這里沒(méi)有什么非常復(fù)雜的事情發(fā)生。該鏈接可以正常運(yùn)行(我沒(méi)有使 e.preventDefault()),但在該行為發(fā)生之前,會(huì)在單擊時(shí)觸發(fā) POST 請(qǐng)求。無(wú)需等待任何形式的響應(yīng)。我只是希望它被發(fā)送到我正在訪問(wèn)的任何服務(wù)。

乍一看,您可能希望該請(qǐng)求的分派是同步的,之后我們將繼續(xù)導(dǎo)航離開(kāi)頁(yè)面,而其他服務(wù)器成功地處理該請(qǐng)求。但事實(shí)證明,情況并非總是如此。

瀏覽器不保證保留打開(kāi)的HTTP請(qǐng)求

當(dāng)瀏覽器中發(fā)生終止頁(yè)面的情況時(shí),并不能保證進(jìn)程內(nèi)的HTTP請(qǐng)求會(huì)成功(參見(jiàn)更多關(guān)于“終止”和頁(yè)面生命周期的其他狀態(tài))。這些請(qǐng)求的可靠性可能取決于幾個(gè)方面——網(wǎng)絡(luò)連接、應(yīng)用程序性能,甚至外部服務(wù)本身的配置。因此,在這些時(shí)刻發(fā)送數(shù)據(jù)可能是不可靠的,如果您依賴這些日志來(lái)做出數(shù)據(jù)敏感的業(yè)務(wù)決策,那么這可能會(huì)帶來(lái)一個(gè)潛在的重大問(wèn)題。為了幫助說(shuō)明這種不可靠性,我使用上面包含的代碼設(shè)置了一個(gè)帶有頁(yè)面的小型 Express 應(yīng)用程序。單擊鏈接時(shí),瀏覽器會(huì)導(dǎo)航到 /other,但在此之前,會(huì)觸發(fā) POST 請(qǐng)求。當(dāng)一切都發(fā)生時(shí),我打開(kāi)了瀏覽器的網(wǎng)絡(luò)選項(xiàng)卡,并且我使用的是“慢 3G”連接速度。一旦頁(yè)面加載并且我已經(jīng)清除了日志,事情看起來(lái)很安靜:

圖片

1.webp

但是一旦鏈接被點(diǎn)擊,事情就會(huì)出錯(cuò),當(dāng)導(dǎo)航發(fā)生時(shí),請(qǐng)求被取消。

圖片

2.webp

這使得我們對(duì)外部服務(wù)是否能夠處理請(qǐng)求缺乏信心。為了驗(yàn)證這種行為,當(dāng)我們使用window.location以編程方式導(dǎo)航時(shí)也會(huì)發(fā)生這種情況:

document.getElementById('link').addEventListener('click', (e) => {
+ e.preventDefault();

// Request is queued, but cancelled as soon as navigation occurs.
fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: 'data'
}),
});

+ window.location = e.target.href;
});

無(wú)論導(dǎo)航以何種方式或何時(shí)發(fā)生,以及活動(dòng)頁(yè)面何時(shí)終止,那些未完成的請(qǐng)求都有被放棄的風(fēng)險(xiǎn)。

但是為什么被取消了呢?

問(wèn)題的根源在于,默認(rèn)情況下,XHR 請(qǐng)求(通過(guò) fetch 或 XMLHttpRequest)是異步且非阻塞的。一旦請(qǐng)求被排隊(duì),請(qǐng)求的實(shí)際工作就會(huì)被移交給幕后的瀏覽器級(jí) API。由于它與性能有關(guān),這很好——你不希望請(qǐng)求占用主線程。但這也意味著當(dāng)頁(yè)面進(jìn)入“終止”狀態(tài)時(shí),它們有被遺棄的風(fēng)險(xiǎn),無(wú)法保證任何幕后工作都能完成。以下是 Google 對(duì)特定生命周期狀態(tài)的總結(jié):

一旦頁(yè)面開(kāi)始被瀏覽器卸載并從內(nèi)存中清除,頁(yè)面就處于終止?fàn)顟B(tài)。在這種狀態(tài)下沒(méi)有新的任務(wù)可以啟動(dòng),并且正在進(jìn)行的任務(wù)如果運(yùn)行時(shí)間過(guò)長(zhǎng)可能會(huì)被殺死。

簡(jiǎn)而言之,瀏覽器的設(shè)計(jì)假設(shè)當(dāng)一個(gè)頁(yè)面被關(guān)閉時(shí),沒(méi)有必要繼續(xù)處理它排隊(duì)的任何后臺(tái)進(jìn)程。

那么,我們有哪些選擇呢?

避免這個(gè)問(wèn)題最明顯的方法可能是,盡可能地延遲用戶操作,直到請(qǐng)求返回響應(yīng)。在過(guò)去,通過(guò)使用XMLHttpRequest中支持的同步標(biāo)志來(lái)實(shí)現(xiàn)這一點(diǎn)是錯(cuò)誤的。使用它會(huì)完全阻塞主線程,導(dǎo)致大量的性能問(wèn)題——我在過(guò)去寫(xiě)過(guò)一些這方面的文章——所以這個(gè)想法甚至不應(yīng)該被接受。事實(shí)上,它正在退出平臺(tái)(Chrome v80+已經(jīng)刪除了它)。相反,如果您打算采用這種類(lèi)型的方法,那么最好在響應(yīng)返回時(shí)等待Promise解析。在它回來(lái)之后,您可以安全地執(zhí)行該行為。使用我們之前的代碼片段,它可能看起來(lái)像這樣:

document.getElementById('link').addEventListener('click', async (e) => {
e.preventDefault();

// Wait for response to come back...
await fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: 'data'
}),
});

// ...and THEN navigate away.
window.location = e.target.href;
});

這可以完成工作,但也有一些不小的缺點(diǎn)。首先,它會(huì)延遲所需行為的發(fā)生,從而損害用戶體驗(yàn)。收集分析數(shù)據(jù)肯定會(huì)給業(yè)務(wù)(以及未來(lái)的用戶)帶來(lái)好處,但讓當(dāng)前用戶為實(shí)現(xiàn)這些好處而支付成本并不理想。更不用說(shuō),作為一個(gè)外部依賴項(xiàng),服務(wù)本身的任何延遲或其他性能問(wèn)題都會(huì)暴露給用戶。如果分析服務(wù)的暫停導(dǎo)致客戶無(wú)法完成一項(xiàng)高價(jià)值的行動(dòng),那么所有人都是輸家。其次,這種方法并不像它最初聽(tīng)起來(lái)那么可靠,因?yàn)橐恍┙K止行為無(wú)法通過(guò)編程延遲。例如, e.preventDefault() 無(wú)法延遲某人關(guān)閉瀏覽器選項(xiàng)卡。因此,它充其量只能涵蓋為某些用戶操作收集數(shù)據(jù),但不足以全面信任它。

指示瀏覽器保留未完成的請(qǐng)求

值得慶幸的是,有一些選項(xiàng)可以保留絕大多數(shù)瀏覽器中內(nèi)置的未完成的 HTTP 請(qǐng)求,并且不需要損害用戶體驗(yàn)。

使用Fetch的keepalive標(biāo)志

如果在使用fetch()時(shí)將keepalive標(biāo)志設(shè)置為true,那么相應(yīng)的請(qǐng)求將保持打開(kāi)狀態(tài),即使發(fā)起該請(qǐng)求的頁(yè)面被終止。使用我們最初的例子,它的實(shí)現(xiàn)如下所示:

<a href="/some-other-page" id="link">Go to Page</a>

<script>
document.getElementById('link').addEventListener('click', (e) => {
fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: "data"
}),
keepalive: true
});
});
</script>

當(dāng)點(diǎn)擊該鏈接并進(jìn)行頁(yè)面導(dǎo)航時(shí),不會(huì)發(fā)生請(qǐng)求取消:

圖片

3.webp

相反,我們得到的是一個(gè)(未知)狀態(tài),原因很簡(jiǎn)單,活動(dòng)頁(yè)面從來(lái)沒(méi)有等待接收任何響應(yīng)。像這樣的一行程序很容易修復(fù),特別是當(dāng)它是常用瀏覽器API的一部分時(shí)。但是,如果您正在尋找一種功能更集中、界面更簡(jiǎn)單的選擇,那么還有另一種方法,它實(shí)際上具有相同的瀏覽器支持。

使用 Navigator.sendBeacon()

Navigator.sendBeacon() 函數(shù)專(zhuān)門(mén)用于發(fā)送單向請(qǐng)求(beacon)。一個(gè)基本的實(shí)現(xiàn)看起來(lái)像這樣,發(fā)送一個(gè)帶有字符串化 JSON 和“text/plain” Content-Type 的 POST:

navigator.sendBeacon('/log', JSON.stringify({
some: "data"
}));

但是此 API 不允許您發(fā)送自定義標(biāo)頭。因此,為了讓我們以“application/json”的形式發(fā)送數(shù)據(jù),我們需要做一些小調(diào)整并使用 Blob:

<a href="/some-other-page" id="link">Go to Page</a>

<script>
document.getElementById('link').addEventListener('click', (e) => {
const blob = new Blob([JSON.stringify({ some: "data" })], { type: 'application/json; charset=UTF-8' });
navigator.sendBeacon('/log', blob));
});
</script>

最后,我們得到了相同的結(jié)果——即使在頁(yè)面導(dǎo)航之后也允許完成的請(qǐng)求。但是還有一些事情可能使它比 fetch() 更有優(yōu)勢(shì):beacon以低優(yōu)先級(jí)發(fā)送。為了演示,當(dāng)同時(shí)使用帶有 keepalive 的 fetch() 和 sendBeacon() 時(shí),Network 選項(xiàng)卡中顯示的內(nèi)容如下:

圖片

4.webp

默認(rèn)情況下,fetch() 獲得“高”優(yōu)先級(jí),而beacon(上面稱(chēng)為“ping”類(lèi)型)具有“最低”優(yōu)先級(jí)。對(duì)于對(duì)頁(yè)面功能不重要的請(qǐng)求,這是一件好事。直接取自 Beacon 規(guī)范:

該規(guī)范定義了一個(gè)接口,[…]最大限度地減少與其他時(shí)間關(guān)鍵操作的資源爭(zhēng)用,同時(shí)確保此類(lèi)請(qǐng)求仍然被處理并交付到目的地。

換句話說(shuō),sendBeacon() 確保它的請(qǐng)求不會(huì)妨礙那些對(duì)您的應(yīng)用程序和用戶體驗(yàn)真正重要的請(qǐng)求。

因?yàn)閜ing屬性而被光榮提及

值得一提的是,越來(lái)越多的瀏覽器支持 ping 屬性。當(dāng)附加到鏈接時(shí),它會(huì)觸發(fā)一個(gè)小的 POST 請(qǐng)求:

<a href="http://localhost:3000/other" ping="http://localhost:3000/log">
Go to Other Page
</a>

這些請(qǐng)求標(biāo)頭將包含單擊鏈接的頁(yè)面(ping-from),以及該鏈接的 href 值(ping-to):

headers: {
'ping-from': 'http://localhost:3000/',
'ping-to': 'http://localhost:3000/other'
'content-type': 'text/ping'
// ...other headers
},

它在技術(shù)上類(lèi)似于發(fā)送beacon,但有一些明顯的限制:

它嚴(yán)格限制在鏈接上的使用,如果您需要跟蹤與其他交互相關(guān)的數(shù)據(jù),例如按鈕點(diǎn)擊或表單提交,這將使其無(wú)法啟動(dòng)。

瀏覽器支持很好,但不是很好。在撰寫(xiě)本文時(shí),F(xiàn)irefox 特別沒(méi)有默認(rèn)啟用它。

您無(wú)法隨請(qǐng)求一起發(fā)送任何自定義數(shù)據(jù)。正如前面提到的,您最多只能得到幾個(gè) ping-*頭文件,以及隨程序一起出現(xiàn)的任何其他頭文件。

綜合考慮,如果您可以發(fā)送簡(jiǎn)單的請(qǐng)求并且不想編寫(xiě)任何自定義 JavaScript,那么 ping 是一個(gè)很好的工具。但是,如果您需要發(fā)送更多實(shí)質(zhì)內(nèi)容,則可能不是最好的選擇。

那么,我應(yīng)該選擇哪一個(gè)呢?

使用 fetch 和 keepalive 或 sendBeacon() 發(fā)送您的最后一秒請(qǐng)求肯定存在權(quán)衡。為了幫助辨別哪種方法最適合不同的情況,需要考慮以下幾點(diǎn):

如果出現(xiàn)以下情況,您可能會(huì)使用 fetch() + keepalive:

  • 您需要輕松地隨請(qǐng)求傳遞自定義標(biāo)頭。
  • 您想向服務(wù)發(fā)出 GET 請(qǐng)求,而不是 POST。
  • 您正在支持較舊的瀏覽器(如 IE)并且已經(jīng)加載了 fetch polyfill。

但在以下情況下 sendBeacon() 可能是更好的選擇:

  • 您正在進(jìn)行簡(jiǎn)單的服務(wù)請(qǐng)求,不需要進(jìn)行太多定制。
  • 您更喜歡更簡(jiǎn)潔、更優(yōu)雅的 API。
  • 您希望確保您的請(qǐng)求不會(huì)與應(yīng)用程序中發(fā)送的其他高優(yōu)先級(jí)請(qǐng)求競(jìng)爭(zhēng)。

原文:https://css-tricks.com/send-an-http-request-on-page-exit/作者:Alex MacArthur

責(zé)任編輯:武曉燕 來(lái)源: 前端全棧開(kāi)發(fā)者
相關(guān)推薦

2022-03-24 14:49:57

HTTP前端

2009-11-09 09:11:17

2023-10-11 17:58:22

2019-11-18 15:50:11

AjaxJavascript前端

2018-05-17 16:25:27

2020-06-17 14:40:49

用戶頁(yè)面前端

2021-03-10 13:19:09

LinuxCPU程序

2025-02-06 08:09:20

POSTGET數(shù)據(jù)

2018-01-30 17:00:10

Linuxscp命令排除文件

2025-02-04 09:58:08

2021-06-02 15:30:44

智能安防物聯(lián)網(wǎng)智能建筑

2017-04-28 09:04:32

移動(dòng)應(yīng)用開(kāi)發(fā)反饋

2021-08-26 06:58:14

Http請(qǐng)求url

2020-03-27 15:10:23

SpringJava框架

2023-07-18 12:50:48

C 語(yǔ)言用戶輸入

2023-11-27 08:57:24

GoGET

2024-03-29 00:00:00

JSAPI網(wǎng)絡(luò)

2023-04-26 08:18:48

FormPrompt表單更改

2022-11-22 08:41:22

curlDELETELinux

2024-07-05 17:49:29

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)