Navigator?sendBeacon頁(yè)面關(guān)閉也能發(fā)送請(qǐng)求方法示例
背景
最近在需求中有一個(gè)這樣的場(chǎng)景:
需要在頁(yè)面關(guān)閉的時(shí)候,用戶(hù)不需要操作,主動(dòng)關(guān)閉當(dāng)前訂單
當(dāng)時(shí)考慮的方案:在頁(yè)面關(guān)閉的時(shí)候,向后端發(fā)送一個(gè)請(qǐng)求,將這個(gè)資源釋放掉;
定下方案時(shí),覺(jué)得也不是什么難事,覺(jué)得谷歌瀏覽器應(yīng)該會(huì)提供頁(yè)面關(guān)閉的 API 供開(kāi)發(fā)者使用。
經(jīng)過(guò)查找,找到了這么兩個(gè) API :beforeunload 和 unload
beforeunload
當(dāng)瀏覽器窗口關(guān)閉或者刷新時(shí),會(huì)觸發(fā) beforeunload 事件。當(dāng)前頁(yè)面不會(huì)直接關(guān)閉,可以點(diǎn)擊確定按鈕關(guān)閉或刷新,也可以取消關(guān)閉或刷新。
window.addEventListener('beforeunload', function (event) { // Cancel the event as stated by the standard. event.preventDefault(); // Chrome requires returnValue to be set. event.returnValue = ''; });
該事件會(huì)使網(wǎng)頁(yè)在離開(kāi)或者刷新的時(shí)候彈出一個(gè)對(duì)話(huà)框,給用戶(hù)一個(gè)提示。在這個(gè)彈框出現(xiàn)時(shí),該頁(yè)面是做不了任何操作的,除非把這個(gè)彈框關(guān)閉。其他頁(yè)面也只能進(jìn)行簡(jiǎn)單的點(diǎn)擊瀏覽操作,鍵盤(pán)是操作不了的。
這個(gè)不符合用戶(hù)無(wú)感知的條件
unload
當(dāng)文檔或一個(gè)子資源正在被卸載時(shí), 觸發(fā) unload 事件。
unload 事件在 beforeunload 事件后觸發(fā),這時(shí)候文檔處于一個(gè)什么狀態(tài)呢?
所有資源都存在,像圖片,iframe的等,但是這些資源對(duì)于用戶(hù)來(lái)說(shuō)均不可見(jiàn),界面上的交互也是無(wú)效的.
使用方式和 beforeunload 相同,但是 unload 事件中不能使用確認(rèn)框,畢竟都已經(jīng)在卸載了
window.addEventListener('unload', function(event) { console.log('unload'); });
問(wèn)題
- 用戶(hù)無(wú)論刷新還是關(guān)閉了頁(yè)面,因?yàn)檫@兩種操作都會(huì)調(diào)用beforeunload和 unload。
- 異步請(qǐng)求會(huì)被 cancel 掉,導(dǎo)致請(qǐng)求無(wú)法發(fā)送成功,由于使用的axios進(jìn)行請(qǐng)求,瀏覽器有幾率關(guān)閉異步請(qǐng)求,造成請(qǐng)求無(wú)法發(fā)出,可以嘗試換成同步。
- 在事件的回調(diào)中使用同步的 AJAX 請(qǐng)求。
window.addEventListener('unload', function (event) { let xhr = new XMLHttpRequest(); xhr.open('post', '/log', false); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('foo=bar'); });
但是谷歌瀏覽器已經(jīng)不允許頁(yè)面關(guān)閉期間進(jìn)行同步的 XMLHTTPRequest(),這條規(guī)則適用于 beforeunload、unload、pagehide和visibilitychange這些 API;
為了確保頁(yè)面在卸載時(shí)講數(shù)據(jù)發(fā)送到服務(wù)器,官方建議使用 sendBeacon()或者 Fetch keep-alive
Navigator.sendBeacon
官方鏈接 https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon
這個(gè)方法主要用于滿(mǎn)足統(tǒng)計(jì)和診斷代碼的需要,這些代碼通常嘗試在卸載(unload)文檔之前向web服務(wù)器發(fā)送數(shù)據(jù)。
Beacon API 有以下這樣幾個(gè)特點(diǎn):
- 通過(guò) HTTP POST 將少量數(shù)據(jù)異步傳輸,可靠性好
- 這個(gè)請(qǐng)求不需要響應(yīng),保證在頁(yè)面的 unload 狀態(tài)從發(fā)起到完成之前被發(fā)送。
- 不會(huì)阻塞頁(yè)面卸載,也就不會(huì)影響下一導(dǎo)航的載入
- 支持跨域
- 不支持自定義請(qǐng)求頭
用法如下:
navigator.sendBeacon(url, data);
url 就是上報(bào)地址,data 可以是 ArrayBufferView,Blob,DOMString 或 Formdata,根據(jù)官方規(guī)范,需要 request header 為 CORS-safelisted-request-header,在這里則需要保證 Content-Type 為以下三種之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
我們一般會(huì)用到 DOMString , Blob 和 Formdata 這三種對(duì)象作為數(shù)據(jù)發(fā)送到后端,下面以這三種方式為例進(jìn)行說(shuō)明。
DOMString
如果數(shù)據(jù)類(lèi)型是 string,則可以直接上報(bào),此時(shí)該請(qǐng)求會(huì)自動(dòng)設(shè)置請(qǐng)求頭的 Content-Type 為 text/plain。
const reportData = (url, data) => { navigator.sendBeacon(url, data); };
Blob
如果用 Blob 發(fā)送數(shù)據(jù),這時(shí)需要我們手動(dòng)設(shè)置 Blob 的 MIME type,一般設(shè)置為 application/x-www-form-urlencoded。
const reportData = (url, data) => { const blob = new Blob([JSON.stringify(data), { type: 'application/x-www-form-urlencoded', }]); navigator.sendBeacon(url, blob); };
Formdata
可以直接創(chuàng)建一個(gè)新的 Formdata,此時(shí)該請(qǐng)求會(huì)自動(dòng)設(shè)置請(qǐng)求頭的 Content-Type 為 multipart/form-data。
const reportData = (url, data) => { const formData = new FormData(); Object.keys(data).forEach((key) => { let value = data[key]; if (typeof value !== 'string') { // formData只能append string 或 Blob value = JSON.stringify(value); } formData.append(key, value); }); navigator.sendBeacon(url, formData); };
注意這里的 JSON.stringify 操作,服務(wù)端需要將數(shù)據(jù)進(jìn)行 parse 才能得到正確的數(shù)據(jù)。
Fetch keep-alive
當(dāng)使用fetch() 方法時(shí),如果把keeplive 設(shè)置為true,即便頁(yè)面被終止請(qǐng)求也會(huì)保持連接。
window.onunload = function() { fetch('/analytics', { method: 'POST', body: "statistics", keepalive: true }); };
以上就是Navigator sendBeacon頁(yè)面關(guān)閉也能發(fā)送請(qǐng)求方法示例的詳細(xì)內(nèi)容,更多關(guān)于Navigator sendBeacon請(qǐng)求發(fā)送的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實(shí)現(xiàn)計(jì)算小于非負(fù)數(shù)n的素?cái)?shù)的數(shù)量算法示例
這篇文章主要介紹了JS實(shí)現(xiàn)計(jì)算小于非負(fù)數(shù)n的素?cái)?shù)的數(shù)量算法,可實(shí)現(xiàn)針對(duì)一定范圍內(nèi)素?cái)?shù)個(gè)數(shù)的統(tǒng)計(jì)功能,涉及javascript數(shù)值運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2019-02-02JS樹(shù)形菜單組件Bootstrap TreeView使用方法詳解
這篇文章主要為大家詳細(xì)介紹了js組件Bootstrap TreeView使用方法,本文一部分針對(duì)于bootstrap的treeview的實(shí)踐,另一部分是介紹自己寫(xiě)的樹(shù)形菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12Javascript 修改String 對(duì)象 增加去除空格功能(示例代碼)
這篇文章主要介紹了Javascript 修改String 對(duì)象 增加去除空格功能(示例代碼)。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11JS拋物線(xiàn)動(dòng)畫(huà)實(shí)例制作
本篇文章給大家詳細(xì)分析了JS拋物線(xiàn)動(dòng)畫(huà)制作過(guò)程以及相關(guān)的代碼實(shí)例分享,有興趣的朋友參考下。2018-02-02JavaScript用Number方法實(shí)現(xiàn)string轉(zhuǎn)int
parseInt方法在format'00'開(kāi)頭的數(shù)字時(shí)會(huì)當(dāng)作2進(jìn)制轉(zhuǎn)10進(jìn)制,所以建議string轉(zhuǎn)int最好用Number方法2014-05-05javascript 日期時(shí)間 轉(zhuǎn)換的方法
javascript 日期時(shí)間 轉(zhuǎn)換的方法,需要的朋友可以參考一下2013-02-02javascript背景時(shí)鐘實(shí)現(xiàn)方法
這篇文章主要介紹了javascript背景時(shí)鐘實(shí)現(xiàn)方法,涉及javascript時(shí)間及頁(yè)面元素樣式的相關(guān)操作技巧,需要的朋友可以參考下2015-06-06