業務場景
當用戶關閉瀏覽器、刷新瀏覽器或者跳轉其他頁面時,向服務器發送一些統計數據。
常規方案
1. 直接發送 xhr 請求
我們會優先想到監聽頁面的unload或者beforeunload事件,在事件回調里使用XMLHttpRequest發送異步請求。
但是由於是xhr請求是異步發送,很可能在它即將發送的時候,頁面已經卸載了,從而導致發送取消或者發送失敗。異步請求響應返回后,由於頁面和相關資源已經卸載,會引起function not found的錯誤。
解決方法就是 AJAX 通信改成同步發送,即只有發送完成,頁面才能卸載。
const syncReport = (url, { data = {}, headers = {} } = {}) => { const xhr = new XMLHttpRequest(); xhr.open('POST', url, false); xhr.withCredentials = true; Object.keys(headers).forEach((key) => { xhr.setRequestHeader(key, headers[key]); }); xhr.send(JSON.stringify(data)); };
將xhr請求改為同步,雖然能夠完成發送數據,但存在以下兩個問題:
部分瀏覽器已經不支持同步的 XMLHttpRequest 對象了(即open()方法的第三個參數為false);
xhr請求改為同步后,會阻塞頁面的卸載和跳轉,導致下一個頁面導航加載的時機變晚,用戶體驗較差。
2. 動態圖片
通過在unload事件處理器中,創建一個圖片元素並設置它的 src 屬性的方法來延遲卸載以保證數據的發送。因為絕大多數瀏覽器會延遲卸載以保證圖片的載入,所以數據可以在卸載事件中發送。
const reportData = (url, data) => { let img = document.createElement('img'); const params = []; Object.keys(data).forEach((key) => { params.push(`${key}=${encodeURIComponent(data[key])}`); }); img.onload = () => img = null; img.src = `${url}?${params.join('&')}`; };
這種方法存在同樣的問題,頁面卸載流程被阻塞,后面頁面的加載時機被延遲,用戶體驗不好
Navigator.sendBeacon
瀏覽器引入的sendBeacon方法,**發出的是異步請求,但是請求是作為瀏覽器任務執行的,與當前頁面是脫鈎的。**因此該方法不會阻塞頁面卸載流程和延遲后面頁面的加載。
1. 基本用法
navigator.sendBeacon(url, data);
url 就是上報地址,data 可以是 ArrayBufferView,Blob,DOMString 或 Formdata,根據官方規范,需要 request header 為 CORS-safelisted-request-header,在這里則需要保證 Content-Type 為以下三種之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
我們一般會用到 DOMString , Blob 和 Formdata 這三種對象作為數據發送到后端,下面以這三種方式為例進行說明。
// 1. DOMString類型,該請求會自動設置請求頭的 Content-Type 為 text/plain const reportData = (url, data) => { navigator.sendBeacon(url, data); }; // 2. 如果用 Blob 發送數據,這時需要我們手動設置 Blob 的 MIME type, // 一般設置為 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); }; // 3. 發送的是Formdata類型, // 此時該請求會自動設置請求頭的 Content-Type 為 multipart/form-data。 var data = { name: '前端名獅子' , age: 20 }; 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); };
sendBeacon 如果成功進入瀏覽器的發送隊列后,會返回true;如果受到隊列總數、數據大小的限制后,會返回false。返回ture后,只是表示進入了發送隊列,瀏覽器會盡力保證發送成功,但是否成功了,無法判斷。
2. 發送數據大小限制
目前沒有給出具體的發送數據大小限制標准,不過有人做了下面的測試,當數據長度是65536時,異步請求進入瀏覽器發送隊列失敗,表明數據大小是有限制,不同的瀏覽器應該有所差別。:
var url = 'http://jsfiddle.net?sendbeacon'; var n = 65536; // sendBeacon limit for Chrome v40 on Windows (2^16) var data = new Array(n+1).join('X'); // generate string of length n if(!navigator.sendBeacon(url, data)) { alert('data limit reached'); }
3. 兼容性
sendBeacon方法存在兼容性問題,除了IE,大部分瀏覽器都已經支持,兼容情況如下圖所示:
考慮到兼容性問題,使用過程中,我們可以采用下面兩種方法做兼容:
不支持時,用同步的xhr替代,也就是上面提到的第一種方法。
navigator.sendBeacon || new Function('var xhr=new XMLHttpRequest();xhr.open("POST",arguments[0],true);r.send(arguments[1]);');
1
補丁文件
// npm 包 npm install navigator.sendbeacon // 外鏈 <script src="https://unpkg.com/navigator.sendbeacon"></script> <!-- OR --> <script src="https://cdn.jsdelivr.net/npm/navigator.sendbeacon"></script>
總結
sendBeacon方法具有如下特點:
發出的是異步請求,並且是POST請求,后端解析參數時,需要注意處理方式;
發出的請求,是放到的瀏覽器任務隊列執行的,脫離了當前頁面,所以不會阻塞當前頁面的卸載和后面頁面的加載過程,用戶體驗較好;
只能判斷出是否放入瀏覽器任務隊列,不能判斷是否發送成功;
Beacon API不提供相應的回調,因此后端返回最好省略response body。
————————————————
版權聲明:本文為CSDN博主「青天訣」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012193330/article/details/102778979
喜歡這篇文章?歡迎打賞~~