解決頁面關閉時數據丟失:用好sendBeacon與fetch的keepalive

不少開發者遇到過這樣的問題:線上數據看板顯示用戶行為數據不全,明明功能已經上線,但收集到的數據卻比預期少很多。
經過排查發現,很多數據丟失發生在用戶關閉頁面或跳轉離開的時候。原本通過fetch發送的請求,因為頁面關閉而被瀏覽器強制取消,導致數據無法成功送達服務器。
問題并不在于代碼邏輯,而是瀏覽器的運行機制決定的。當用戶關閉標簽頁時,瀏覽器會立即停止當前頁面的所有JavaScript執行,包括尚未完成的網絡請求。這意味著,即使用戶已經觸發了數據上報,這些請求也可能在到達服務器前就被中斷了。
navigator.sendBeacon的解決方案
從2014年開始,瀏覽器提供了一個專門的api來解決這個問題:navigator.sendBeacon()。這個API的設計目的就是在頁面卸載時可靠地發送數據。
與fetch不同,sendBeacon不會等待服務器響應,也不會延遲頁面的關閉過程。它會將請求放入瀏覽器的發送隊列,然后在后臺完成傳輸,即使JavaScript已經停止執行也不會影響發送。
對比一下兩種寫法的區別:
// 這種寫法在頁面關閉時可能失敗
window.addEventListener('beforeunload', () => {
fetch('/analytics', {
method: 'POST',
body: JSON.stringify({ event: 'page_exit' })
}); // 可能被取消
});
// 使用sendBeacon更可靠
window.addEventListener('beforeunload', () => {
navigator.sendBeacon('/analytics',
JSON.stringify({ event: 'page_exit' })
); // 能夠成功進入發送隊列
});當用戶快速關閉頁面或跳轉到其他頁面時,這兩種方式的差異非常明顯。瀏覽器通常會取消fetch請求以確保頁面快速關閉,而sendBeacon是專門為此場景設計的。
實際效果對比
實際測試數據顯示,navigator.sendBeacon()在頁面切換時的數據送達率達到95.8%,而傳統的fetch()方法只有84.9%。在移動端,這個差距更加明顯。由于移動設備上更激進的內存回收機制和應用后臺運行限制,fetch的失敗率更高,而sendBeacon則能保持較好的穩定性。
sendBeaconAPI特別適合處理小型但重要的數據上報:
- 更高的可靠性:請求進入瀏覽器原生隊列,能夠跨越頁面卸載的窗口
- 不會阻塞頁面:不會拖慢用戶的導航體驗
- 使用簡單:不需要復雜的備用方案或龐大的埋點庫
使用限制和注意事項
sendBeacon有兩個主要限制:單次請求大小不能超過64KB,并且多個beacon請求共享這個大小限制。
// 正好64KB,可以正常發送
navigator.sendBeacon('/log', 'A'.repeat(65536));
// 超過限制,會返回false表示失敗
navigator.sendBeacon('/log', 'A'.repeat(65537));sendBeacon()返回一個布爾值,表示請求是否成功進入隊列。建議總是檢查這個返回值并提供備用方案:
const success = navigator.sendBeacon('/analytics', data);
if (!success) {
// 備用方案:使用fetch或拆分數據
fetch('/analytics', {
method: 'POST',
body: data
});
}另一個需要注意的限制是sendBeacon不支持自定義請求頭。當發送字符串數據時,瀏覽器會使用默認的text/plain Content-Type,服務器端需要相應處理。
如果需要發送JSON數據,可以使用Blob對象,但這會觸發CORS預檢請求:
const data = { event: 'click', count: 42 };
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json'
});
navigator.sendBeacon('/analytics', blob);
// 注意:這會觸發CORS預檢請求大多數團隊選擇在服務器端解析text/plain內容,以避免額外的預檢請求開銷。
更多應用場景
除了頁面退出埋點,sendBeacon還非常適合實時錯誤上報:
window.addEventListener('error', (event) => {
const errorData = {
message: event.message,
filename: event.filename,
line: event.lineno,
stack: event.error?.stack,
timestamp: Date.now()
};
navigator.sendBeacon('/errors', JSON.stringify(errorData));
});對于用戶交互追蹤,sendBeacon也是很好的選擇:
document.addEventListener('click', (event) => {
const clickData = {
element: event.target.tagName,
x: event.clientX,
y: event.clientY,
timestamp: Date.now()
};
navigator.sendBeacon('/interactions', JSON.stringify(clickData));
});現代替代方案:fetch with keepalive
近年來,另一種解決方案變得越來越流行:使用fetch的keepalive選項。
fetch('/analytics', {
method: 'POST',
body: JSON.stringify(data),
keepalive: true,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
}
});這種方式既保持了Beacon在頁面卸載期的可靠性,又保留了fetch的靈活性,包括自定義請求頭、豐富的HTTP方法和對響應的處理能力。到2025年,主流瀏覽器都對keepalive提供了很好的支持。
如何選擇
選擇navigator.sendBeacon()當:
- 需要輕量級的埋點或日志記錄
- 處理頁面退出或會話結束時的數據
- 進行錯誤上報和診斷
- 追求最大兼容性和最簡單實現
選擇fetch({ keepalive: true })當:
- 需要自定義請求頭或認證信息
- 需要處理服務器響應或復雜HTTP配置
- 需要更通用的請求能力
瀏覽器兼容性
sendBeaconAPI在現代瀏覽器中已有很好的支持,覆蓋約92%的用戶:
- Chrome 39+
- Firefox 31+
- Safari 11.1+
- Edge 14+
Internet Explorer不支持此API,但在2025年這已經不再是主要問題。為了最大程度的兼容性,可以添加特性檢測和備用方案:
if (navigator.sendBeacon) {
navigator.sendBeacon('/log', data);
} else {
// 回退到XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', '/log', false);
xhr.send(data);
}總結
navigator.sendBeacon()和fetch with keepalive徹底解決了頁面關閉時數據丟失的問題。無論是用戶快速關閉頁面還是跳轉到其他網站,重要的數據都能可靠地送達服務器。
對于需要準確收集用戶行為數據的應用來說,正確使用這些API是確保數據完整性的關鍵。





























