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

離開頁面時,你知道如何可靠地發(fā)送一個 HTTP 請求嗎?

開發(fā) 前端
當(dāng)頁面因為某些原因被終止時,瀏覽器是沒法保證正在進(jìn)行中的 HTTP 請求能夠成功完成。這些請求的可信度取決于多個因素 —— 網(wǎng)絡(luò)連接、程序性能甚至是外部服務(wù)器自身的配置。

在某些情況下,當(dāng)用戶跳轉(zhuǎn)到其他頁面或者提交一個表單的時候,我需要發(fā)送一個 HTTP 請求,用于把一些數(shù)據(jù)記錄到日志中。思考如下場景——當(dāng)一個鏈接被點(diǎn)擊時,需要發(fā)送一些信息到外部服務(wù)器:

<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"
    }, 
    bodyJSON.stringify({
      some"data"
    })
  });
});
</script>

這個示例并不復(fù)雜。鏈接的跳轉(zhuǎn)行為仍然會正常的執(zhí)行(我并沒有使用 e.preventDefault() 去阻止),但是在這個行為發(fā)生之前,單擊事件會觸發(fā)一個 POST 請求。我們只需要它發(fā)送到我們正在訪問的服務(wù)即可,而不需要等待這個請求返回。

乍一看你可能會覺得處理這個請求是同步的,請求發(fā)出后,在我們繼續(xù)跳頁面的同時,其他服務(wù)器會成功地處理這個請求。但事實上,情況并非總是如此。

瀏覽器不能保證持續(xù)保持 HTTP 請求的打開狀態(tài)

當(dāng)頁面因為某些原因被終止時,瀏覽器是沒法保證正在進(jìn)行中的 HTTP 請求能夠成功完成(了解更多[1]關(guān)于頁面的“終止”以及頁面生命周期的其他狀態(tài))。這些請求的可信度取決于多個因素 —— 網(wǎng)絡(luò)連接、程序性能甚至是外部服務(wù)器自身的配置。

因此,這種情況下發(fā)出的數(shù)據(jù)可靠性很糟,如果你的業(yè)務(wù)決策依賴這些日志數(shù)據(jù),這可能會帶來一個潛在的重大隱患。

為了說明這種場景的不可靠性,我編寫了一個基于 Express 的簡單應(yīng)用,并使用以上代碼實現(xiàn)了一個頁面。當(dāng)點(diǎn)擊鏈接時,瀏覽器會導(dǎo)航到 /other,但此之前,會觸發(fā)一個 POST 請求。

開始之前,我會將開發(fā)者工具的“網(wǎng)絡(luò)”標(biāo)簽打開,使用“低速3G”連接速度。一旦頁面加載完成,我就清除日志,事情看起來相當(dāng)正常:

圖片1

但是一旦我單擊了鏈接,事情就不太對了。當(dāng)頁面導(dǎo)航發(fā)生的時候,POST 請求就被取消了。

圖片2

這使得我們對外部服務(wù)實際上能夠處理完這個請求沒有足夠的信心。為了驗證這個行為,當(dāng)我們以編程方式使用 ??window.location?? 導(dǎo)航時,相同的情況也會發(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"
    }, 
    bodyJSON.stringify({
      some'data'
    }),
  });

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

無論導(dǎo)航是如何或何時發(fā)生的,以及活動頁面是如何終止的,那些未完成的請求都有被拋棄的風(fēng)險。

但是它們?yōu)槭裁磿蝗∠兀?/span>

問題的根源在于,默認(rèn)情況下 XHR 請求(通過 fetch 或 XMLHttpRequest)是異步且非阻塞的。一旦請求進(jìn)入隊列,請求的實際工作就會交給后臺的瀏覽器級 API。

從性能考慮,這是正確的行為——你并不會希望主線程被請求給堵塞。但是這會帶來一個風(fēng)險,就是當(dāng)頁面進(jìn)入“終止”狀態(tài)時,這些請求會被拋棄,這就導(dǎo)致了在后臺運(yùn)行的服務(wù)不能保證正確完成。這是谷歌對于這個特定生命周期狀態(tài)的總結(jié)[2]

頁面瀏覽器開始卸載頁面并對其內(nèi)存清理時,該頁面就進(jìn)入終止?fàn)顟B(tài)。在此狀態(tài)下,不會執(zhí)行任何新任務(wù)[3],同時正在處理中的任務(wù)如果運(yùn)行時間過長可能會被殺死。

簡單來說,瀏覽器的設(shè)計是基于這樣的假設(shè):只要頁面關(guān)閉時,后臺隊列中的任何進(jìn)程都不需要再繼續(xù)執(zhí)行。

所以我們有沒有別的選擇?

似乎避免這個問題最直接的方法是盡可能地延遲用戶操作,直到請求的響應(yīng)返回。在過去,通過使用 XMLHttpRequest 支持的同步標(biāo)志[4]來實現(xiàn)。但這是錯誤的,因為使用這種方式會完全的阻斷主線程,從而造成一大堆的性能問題——關(guān)于這個問題我曾寫過一些東西[5]——所以不要考慮這種方式了。事實上,平臺也正在移除這種方式(Chrome v80+ 已經(jīng)將其移除[6])。

即使你仍打算采用這種方式,也最好使用 Promise 并在其響應(yīng)返回時執(zhí)行 resolve。這樣你就可以安全地執(zhí)行該行為。對上面我們示例的代碼進(jìn)行修改:

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"
    }, 
    bodyJSON.stringify({
      some'data'
    }),
  });

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

這樣就可以完成工作了,但存在的缺點(diǎn)也不容忽視。

首先,它會使期望的行為延遲發(fā)生,這會降低用戶體驗。 收集分析數(shù)據(jù)當(dāng)然會給商務(wù)(或許也會對潛在用戶)帶來收益,但為此收益讓既有用戶付出代價就不是一個好的選擇了。更不用說,作為外部依賴,服務(wù)本身的任何延遲或其他性能問題都將暴露給用戶。如果因為分析服務(wù)的超時導(dǎo)致了客戶無法完成高價值的操作,那么所有人都將蒙受損失。

其次,這種方法并不像聽起來那樣可靠,因為一些終止行為不能通過編程方式延遲。 例如,??e.preventDefault()?? 在延遲關(guān)閉瀏覽器標(biāo)簽時是不起作用的。所以,最好的情況下,這種方式可以涵蓋一些用戶行為的數(shù)據(jù)收集,但缺乏足夠的可信度。

指示瀏覽器保持未完成的請求

值得高興的是,絕大多數(shù)瀏覽器都內(nèi)置了保持未完成 HTTP 請求的能力,而且不需要犧牲用戶體驗。

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

當(dāng)使用 fetch() 方法時,如果把 keeplive 標(biāo)志[7]設(shè)置為 true,即便頁面被終止請求也會保持連接。對我們最初的用例進(jì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"
      }, 
      bodyJSON.stringify({
        some"data"
      }), 
      keepalivetrue
    });
  });
</script>

當(dāng)單擊鏈接時,頁面進(jìn)行跳轉(zhuǎn),但是請求沒有被取消。

圖片3

事實上,我們是留下了一個(unknown)狀態(tài),這只是因為活動頁面不會等待接收任何類型的響應(yīng)。

只需要添加這樣一行代碼,使得修復(fù)這個問題看起來很簡單,特別是當(dāng)它被常見瀏覽器的 API 支持時。但如果你想尋找一個更專業(yè)的接口方式,還有另外一種幾乎相同受到瀏覽器支持的方法。

使用 Navigator.sendBeacon() 方法

??sendbeacon()?? 方法專門用于發(fā)送單向請求(beacons[8])。一個基本的實現(xiàn)是這樣的,發(fā)送一個帶有 JSON 字符串和一個 Content-Type 是 "text/plain" 的 POST 請求:

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

但是這個 API 并不允許你設(shè)置自定義的 headers。所以,為了方便我們使用 "application/json" 格式發(fā)送數(shù)據(jù),我們需要使用 Blob 做一點(diǎn)小的調(diào)整:

<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é)果——請求在頁面跳轉(zhuǎn)之后也可以完成。但是,還有一些情況下可能會讓它比 fetch() 更有優(yōu)勢: beacons 以低優(yōu)先級發(fā)送。

為了演示說明,以下是 Network 選項卡中同時使用帶 keepalive 的 fetch() 和 sendBeacon() 時的情況:

圖片4

默認(rèn)情況下,fetch() 獲得一個 “高” 優(yōu)先級,而 beacon(上圖中的 “ping” 類型) 具有 “最低” 優(yōu)先級。對于那些對頁面功能不是很重要的請求,這是一件好事。直接引用 Beacon規(guī)范[9]:

該規(guī)范定義了一個接口,該接口 […] 在確保此類請求仍然得到處理并交付到目的地的情況下,最大限度地減少了其與其他時間敏感操作的資源競爭。

換個說法就是,sendBeacon() 方法確保了那些程序中真正的關(guān)鍵過程和用戶體驗不會受到影響。

給 ping 屬性榮譽(yù)提名

值得一提的是越來越多的瀏覽器開始支持 ping 屬性[10]。當(dāng)在鏈接上設(shè)置該屬性時,鏈接被點(diǎn)擊時會觸發(fā)一個小型的 POST 請求:

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

這些請求 headers 里會帶著鏈接所在頁面的地址(ping-from)以及鏈接 href 指向的地址(ping-to):

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

這在技術(shù)上很接近發(fā)送一個 beacon,但是有一些需要注意的限制:

1. 它被嚴(yán)格的限制只能在超鏈接使用。你不能將它用于跟蹤與其他交互相關(guān)的數(shù)據(jù),比如按鈕點(diǎn)擊或表單提交。

2. 大部分瀏覽器支持的很好,但不是所有[11]。在撰寫本文時,F(xiàn)irefox還沒有默認(rèn)啟用這個功能。

3. 你不能使用其發(fā)送自定義的數(shù)據(jù)。如前面提到的,除了請求本身包含的 header 信息外,你最多在 header 中額外獲得幾個 ping-*。

考慮以上所有因素,如果你只是要求發(fā)送簡單的請求,并且不想編寫任何自定義 JavaScript,那么 ping 是一個很好的工具。但如果你需要發(fā)送一些更有意義的東西,這就不是最好的選擇。

那么,究竟應(yīng)該如何選擇?

是使用 keep-alive 標(biāo)志的 fetch,還是用 sendBeacon 來發(fā)送頁面終止時的請求肯定需要權(quán)衡。以下建議或許可以幫助你在不同情況下做出正確的選擇:

以下情況可以選擇 fetch() + keepalive:

  • 你需要簡單的發(fā)送自定義 headers 的請求
  • 你需要使用 GET 而非 POST
  • 你需要兼容老舊的瀏覽器(例如 IE),并已經(jīng)有了一個 fetch 方法的 polyfill

以下情況使用 sendBeacon() 或許更好:

  • 你只需要發(fā)送一個簡單的服務(wù)請求,而不需要太多的定制化
  • 你喜歡更簡約更優(yōu)雅的代碼方式
  • 你需要保證該請求不會和其他更重要的請求競爭資源

不要再踩我踩過的坑

我之所以會去深入探究頁面終止時瀏覽器是如何處理進(jìn)行中的請求,是因為一段時間以前,我的團(tuán)隊發(fā)現(xiàn),當(dāng)我們開始在表單提交時發(fā)送特定分析請求后,該類型的分析日志的收集率突然發(fā)生了變化。這一變化是突然而顯著的——比之前下降了約30%。

通過深入研究這個問題產(chǎn)生的原因,找到了避免它的工具,從而挽救了局面。所以,如果可以的話,我希望我對這些小挑戰(zhàn)的理解,能夠幫助你們避免那些我們曾踩過的坑。讓記日志變得更加愉快!

參考資料

[1]了解更多: ?https://developers.google.com/web/updates/2018/07/page-lifecycle-api?

[2]這是谷歌對于這個特定生命周期狀態(tài)的總結(jié): ?https://developers.google.com/web/updates/2018/07/page-lifecycle-api#states?

[3]新任務(wù): ?https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task?

[4]同步標(biāo)志: ?https://xhr.spec.whatwg.org/#synchronous-flag?

[5]關(guān)于這個問題我曾寫過一些東西: ?https://macarthur.me/posts/use-web-workers-for-your-event-listeners?

[6]已經(jīng)將其移除: ?https://developers.google.com/web/updates/2019/12/chrome-80-deps-rems?

[7]keeplive 標(biāo)志: ?https://fetch.spec.whatwg.org/#request-keepalive-flag?

[8]beacons: ?https://w3c.github.io/beacon/#sec-processing-model?

[9]Beacon規(guī)范: ?https://www.w3.org/TR/beacon/?

[10]ping 屬性: ?https://css-tricks.com/the-ping-attribute-on-anchor-links/?

[11]但不是所有: ?https://caniuse.com/ping?

[12]參考原文: ??https://css-tricks.com/send-an-http-request-on-page-exit/??

責(zé)任編輯:龐桂玉 來源: 前端大全
相關(guān)推薦

2022-07-03 17:55:53

HTTP頁面瀏覽器

2023-10-11 17:58:22

2009-11-09 09:11:17

2018-05-17 16:25:27

2015-04-29 10:02:45

框架如何寫框架框架步驟

2020-10-16 15:06:59

開發(fā)技術(shù)方案

2022-05-09 10:47:08

登錄SpringSecurity

2023-11-27 08:57:24

GoGET

2019-11-14 16:05:29

TCPHTTP前端

2020-10-20 14:01:16

HTTP

2019-11-18 15:50:11

AjaxJavascript前端

2021-08-26 06:58:14

Http請求url

2021-03-10 13:19:09

LinuxCPU程序

2019-07-09 06:13:09

TCPHTTP網(wǎng)絡(luò)協(xié)議

2024-01-26 11:08:57

C++函數(shù)返回不同類型

2010-11-19 09:16:38

2020-04-08 08:35:20

JavaScript模塊函數(shù)

2018-07-30 16:31:00

javascriptaxioshttp

2021-10-29 12:01:11

HTTP代碼前端

2020-02-05 14:05:21

Java技術(shù)數(shù)組
點(diǎn)贊
收藏

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