Navigator.sendBeacon 無阻塞發送統計數據


業務場景
當用戶關閉瀏覽器、刷新瀏覽器或者跳轉其他頁面時,向服務器發送一些統計數據。

常規方案
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

喜歡這篇文章?歡迎打賞~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM