前一段時間查看前端日志監控的時候發現,有很多關鍵業務節點埋點及用戶行為軌跡數據丟失,而且丟失率達到16%這么高,梳理了一下工程里的前端埋點邏輯及方法,發現存在很大漏洞,做了一期優化,使得日志丟失率不足0.1%,使用了瀏覽器提供的發送保障的更簡潔的sendBeacon方法,以下是對sendBeacon方法的一些理解
用戶卸載網頁的時候,有時需要向服務器發一些數據。很自然的做法是在unload事件或beforeunload事件的監聽函數里面,使用XMLHttpRequest對象發送數據。但是,這樣做不是很可靠,因為XMLHttpRequest對象是異步發送,很可能在它即將發送的時候,頁面已經卸載了,從而導致發送取消或者發送失敗。 解決方法就是 AJAX 通信改成同步發送,即只有發送完成,頁面才能卸載。但是,很多瀏覽器已經不支持同步的 XMLHttpRequest 對象了(即open()方法的第三個參數為false)
window.addEventListener('unload', logData, false);
function logData() {
var client = new XMLHttpRequest();
// 第三個參數表示同步發送
client.open('POST', '/log', false);
client.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
client.send(analyticsData);
}
上面代碼指定XMLHttpRequest同步發送,很多瀏覽器都已經不支持這種寫法。
而且將XMLHttpRequest同步發送,會延遲頁面的卸載及下一個頁面的載入,性能非常的差
同步通信有幾種變通的方法:
1、一種做法是新建一個<img>元素,數據放在src屬性,作為 URL 的查詢字符串,這時瀏覽器會等待圖片加載完成(服務器回應),再進行卸載。
使用new Image有可能遇到aborted,導致無法成功發送
2、另一種做法是創建一個循環,規定執行時間為幾秒鍾,在這幾秒鍾內把數據發出去,然后再卸載頁面。
這些做法的共同問題是,由於同步請求的阻塞,卸載的時間被硬生生拖長了,后面頁面的加載被推遲了,用戶體驗不好。
navigator.sendBeacon() 方法可用於通過HTTP將少量數據異步傳輸到Web服務器。
navigator.sendBeacon(url, data);
為了解決這個問題,瀏覽器來提供發送保障的更簡潔的sendBeacon方法。sendBeacon是異步的,不會影響當前頁到下一個頁面的跳轉速度,且不受同域限制。這個方法還是異步發出請求,但是請求與當前頁面脫離關聯,作為瀏覽器的任務,因此可以保證會把數據發出去,不拖延卸載流程。
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon('/log', analyticsData);
}
Navigator.sendBeacon方法接受兩個參數,第一個參數是目標服務器的 URL,第二個參數是所要發送的數據(可選),可以是任意類型(字符串、表單對象、二進制對象等等)。
sendBeacon 如果成功進入瀏覽器的發送隊列后,會返回true;如果受到隊列總數、數據大小的限制后,會返回false。返回ture后,只是表示進入了發送隊列,瀏覽器會盡力保證發送成功,但是否成功了,不會再有任何返回值。目前暫無具體的數據長度限制標准。
navigator.sendBeacon(url, data)
// HTML 代碼如下 // <body "analytics('start')" οnunlοad="analytics('end')"> function analytics(state) { if (!navigator.sendBeacon) return; var URL = 'http://example.com/analytics'; var data = 'state=' + state + '&location=' + window.location; navigator.sendBeacon(URL, data); }
sendBeacon() 方法存在的意義:
使用 sendBeacon() 方法會使用戶代理在有機會時異步地向服務器發送數據,同時不會延遲頁面的卸載或影響下一導航的載入性能。這就解決了提交分析數據時的所有的問題:數據可靠,傳輸異步並且不會影響下一頁面的加載。此外,代碼實際上還要比其他技術簡單許多。
最新的瀏覽器兼容情況:

考慮到對目前瀏覽器的支持情況,需要做一下降級支持(如同步模式下的xhr,如果不是同域則要支持CORS):
navigator.sendBeacon || new Function('var xhr=new XMLHttpRequest(); xhr.open("POST",arguments[0],true);r.send(arguments[1]);'); example: function SendBeacon(src) if (typeof(navigator.sendBeacon) == "function") { var b = new Blob([], {type: 'application/x-www-form-urlencoded'}); return navigator.sendBeacon(src, b); } return false; }

