在web項目開發中,關於瀏覽器關閉事件有兩個很常見的問題:為什么我沒有監聽瀏覽器關閉事件? 我監聽到了這個事件,但寫在事件里的異步請求為什么發送不成功?
原因分析:這兩個問題無外乎兩個原因:瀏覽器關閉事件未被觸發 和 異步請求發送失敗。
原因1:關閉瀏覽器時一定會觸發事件嗎?如果不一定,那什么條件下才不觸發呢?
與瀏覽器關閉事件相關事件有onunload和onbeforeunload兩個。區別在於onbeforeunload在onunload之前執行,它還可以阻止onunload的執行。因此我們着重關注onbeforeunload事件。簡單科普一下onbeforeunload事件。
當窗口即將被卸載(關閉)時,會觸發該事件.此時頁面文檔依然可見,且該事件的默認動作可以被取消. 該函數應當返回一個字符串,當返回的字符串不為null或者undefined時,彈出確認窗口讓用戶自行選擇是否關閉當前頁面。一些瀏覽器將該事件返回的字符串顯示在彈出窗上。
既然“當窗口即將被卸載(關閉)時,會觸發該事件“,那也就是說只要我關閉快就一定會觸發該事件嘍?然而當關閉瀏覽器時,未必一定會觸發onbeforeunload事件。MDN上關於這個事件的觸發條件是這樣描述的。
為避免意外彈出窗口,除非頁面已與之交互,否則瀏覽器可能不會顯示在beforeunload事件中創建的提示,甚至根本不會顯示它們。
那什么時候算是非與之交互呢?這里舉個例子,一個頁面連着刷新兩次,第二次刷新時,就認為非與之交互,就不會觸發onbeforeunload事件。,同時對於觸發條件,各個瀏覽器之間也存在差異。具體差異匯總表如下:
說明一下,瀏覽器關閉事件(onbeforeunload)里已經不可以自定義彈出窗信息了。MDN中明確寫道:
一些瀏覽器將該事件返回的字符串顯示在彈出窗上。但從Firefox 4、 Chrome 51、Opera 38 和Safari 9.1開始,通用確認信息代替事件返回的字符串。
原因2:異步請求發送失敗了嗎?
一定失敗。原因發送異步請求后,隨即關閉了瀏覽器,這時候這次請求的”三次握手”的”第三次握手”,客戶主機便不會響應服務器主機,這也就成了一個失敗的請求。如圖所示。
第三次握手失敗
那么有什么解決辦法嗎?
- 關閉瀏覽器時發送同步請求,來保證請求發送完成。但是這樣一來會產生如下問題:
a) 頁面延遲幾秒后再關閉,體驗糟糕。
b) XMLHttpRequest規范中禁止在這個事件處理器中同步調用接口。
使用
XMLHttpRequest
發送同步請求的方式已經計划從規范中刪除,不再建議開發者使用。
這個時候不熟悉XMLHttpRequest的同學也許會問:等一下,我的代碼里根本就沒有XMLHttpRequest這個對象,所以他的規范憑什么約束我?
相信你項目里調用接口時已經用到了ajax庫,或者axios庫。其實現有的ajax庫都是對XMLHTTPRequest對象的一種封裝,而axios是通過promise實現對ajax技術的一種封裝,這樣一來一切都說得通了。原來我們都在直接或者間接的使用着XMLHttpRequest對象。
2.Fetch 的keepalive屬性
Fetch API提供了一套健壯的與服務器端交互的方式,提供了跨越不同平台 API 的一致接口。它提供了一個keepalive屬性,保證不管發送請求的頁面關閉與否,請求都會持續直到結束。不過上傳數據的限制是64 KB。寫法如下:
window.addEventListener(‘onbeforeunload’, { fetch('/siteAnalytics', { method: 'POST', body: getStatistics(), keepalive: true }); }
那通過Fetch API調用的接口如何添加頭信息呢?以添加token為例,以下代碼親測有效。
window.addEventListener(‘onbeforeunload’, { fetch('/siteAnalytics', { method: 'POST', body: 'id=' + id + '&name=' + name + '&age=' + age, headers: { 'Content-Type': 'application/x-www-form-urlencoded', token:’ myToken’ }, keepalive: true }); }
3.SendBeacon()
SendBeacon() 方法可用於通過HTTP將少量數據異步傳輸到Web服務器。該方法底層的使用的是 Fetch API,這樣就能明白為什么它也有少量數據(64 KB)的上傳數據限制,也能明白為什么它還能在頁面卸載后繼續請求。它的主要優點是簡單,只要用一行代碼就能搞定。
window.addEventListener('unload', { navigator.sendBeacon('/siteAnalytics', getStatistics()); }
總結一下,對於瀏覽器關閉事件,如果我們與頁面未發生交互,那么當窗口即將被卸載(關閉)時便不會觸發onbeforeunload事件;同時一些主流瀏覽器,從某個版本開始,也不允許我們自定義彈窗信息來給予用戶友好提示了;如果想在該事件中發送請求,相對於使用XMLHttpRequest對象來說,fetch API或者sendBeacon()或是更好的選擇。