背景:
有一個任務非常耗時會消耗後臺大量算力,所以在退出頁面的時候,要求前端這邊傳送一個請求來殺死任務。
一開始以為這個需求非常簡單,就是在進入其他路由前,傳送一下請求,殺死一下任務就好了。
然而現實狠狠的打了我的臉,因為退出頁面的場景不止切換路由~
退出頁面場景:
- 還在本網站,跳到其他路由
- 重新整理頁面/關閉頁面也需要傳送請求來殺死任務
還在本網站,跳到其他路由
這個比較簡單,在Vue
中可以通過路由離開的鉤子beforeRouteLeave
來實現:
beforeRouteLeave(to, from, next) {
if (任務執行中) {
// 傳送請求
}else{
next(true) // 使用者離開
}
}
重新整理頁面/關閉頁面的情況:
然而在重新整理頁面的時候,beforeRouteLeave
並不會執行,接著想到了下面這兩個API
.
beforeunload
和unload
beforeunload 當瀏覽器視窗關閉或者重新整理時觸發:
介紹:
使用這個API
可以阻止頁面直接關閉,使用者通過點選確定/取消按鈕,來決定是否不關閉/重新整理當前頁面。
在 chrome 下長這個樣子,你們肯定都見過:
如何使用
這個 API 的使用非常簡單,只要在頁面載入的時候監聽一下此事件,在需要出現彈窗的時候return 一個可以轉化為 true 的值,就可以了。
// 頁面解除安裝之前
let killTask = false; // 是否殺死任務
window.onbeforeunload = e => {
if (任務執行 && 對應頁面) {
killTask = true;
return '您可能有資料沒有儲存'; // 在部分瀏覽器可以修改彈窗標題
} else {
killTask = false;
}
// 沒有return一個可以轉化為true的值 就不會出現彈窗
};
出現此彈窗的瀏覽器行為:
以下行為是基於 chorme:
- 焦點:你沒有點選取消/確定之前,焦點會一直在此彈窗上
- 你無法在出現彈窗的頁面上執行任何操作
- 在其他頁面也只能執行簡單的點選操作,彈窗還是存在頁面中間,無法使用鍵盤,
- 鍵盤:鍵盤被繫結在彈窗上,只能通過按鍵
Esc
、Enter
來執行取消/確定操作 - 彈窗不是頁面的 dom,是瀏覽器的行為
- 使用者取消/確定,沒有回撥 API,無法得知
彈窗標題:
chrome 中重新整理頁面的標題:重新載入此網站?
chrome 中關閉頁面的標題:離開此網站?
現在大部分瀏覽器都不允許修改彈窗的標題,這個是為了安全考慮,來保證使用者不受到錯誤資訊的誤導,
迷茫:
一開始我以為既然可以攔截到使用者的重新整理/關閉頁面的操作,出現了上面那個彈窗,這個需求就已經做完了的時候。
然後發現,瀏覽器竟然沒有提供使用者點選確定/取消重新整理頁面的回撥。
到這裡我陷入了迷茫,盯著beforeunload
這個 API 思考了起了人生的意義(其實是在發呆),盯著盯著,從beforeunload
的before
我也就想到了unload
這個 API。
瞬間又燃起了鬥志,何不試試這個unload
?
unload
當頁面正在被解除安裝的時候觸發該事件
介紹
當頁面正在被解除安裝的時候觸發該事件,該事件不可取消,為不可逆操作。
使用
直接監聽該事件就可以了。
window.onunload = e => {}
結合需求:
killTask
為beforeunload
時定義的變數,每次進入回撥,都會給killTask
賦值,使用這個值就可以判斷什麼時候可以傳送請求殺死任務。
window.onunload = e => {
if (killTask && 對應頁面) {
// 傳送請求
}
};
到這裡大家肯定以為已經做出來了該需求,事實上,並沒有!
無法傳送非同步請求
我使用的是axios
來傳送請求,請求發出去了,但是被取消了,伺服器那邊根本沒有收到請求,如下。
經過一頓分析:發現是axios
請求是非同步的問題,谷歌之後發現axios不支援同步的請求
最後使用原生的XMLHttpRequest物件,讓請求同步
大功告成! 實際上,上面才是我第一次要發的內容,而下面更好的解決方法!
缺陷與更好的建議:
當我把這篇文章釋出在公眾號上,被奇舞週刊轉載了,上面一些大佬給我提了一些建議。
研究了一下,結果...好吧!我承認我是菜雞。
hey~ 不過這正是我寫部落格的收穫之一,分享經驗,收穫知識!
效能缺陷:
XHR同步請求會阻礙頁面解除安裝,如果是重新整理/跳轉頁面的話,頁面重新展示速度會變慢,導致效能問題。
畢竟向網路傳送請求並獲得響應可能會超級慢,有可能是使用者網路環境比較差,又或者是伺服器掛了,請求一直沒返回回來...
基於效能問題,大佬們推薦使用Beacon代替XHR,然後經過一番搜尋...
Beacon API
- Beacon API用於將少量資料通過post請求傳送到伺服器。
Beacon
是非阻塞請求,不需要響應
完美解決效能缺陷問題:
- 瀏覽器將
Beacon
請求排隊讓它在空閒的時候執行並立即返回控制 - 它在
unload
狀態下也可以非同步傳送,不阻塞頁面重新整理/跳轉等操作。
所以Beacon
可以完美解決上面提到的因XHR同步請求阻塞而引起的效能缺陷問題。
使用:navigator.sendBeacon()
完整API:
let result = navigator.sendBeacon(url, data);
Beacon
是掛在navigator
下面的,上面就是它的完整API。
result
是一個布林值,代表這次傳送請求的結果:
- 如果瀏覽器接受並且把請求排隊了則返回 tru
- 如果在這個過程中出現了問題就返回 false
navigator.sendBeacon
接受兩個引數:
- url: 請求的 URL。請求是 POST 請求。
- data: 要傳送的資料。 資料型別可以是:ArrayBufferView, Blob, FormData,Sting。
來看一個用FormData
來傳遞資料的栗子,你就懂了:
// 建立一個新的 FormData 並新增一個鍵值對
let data = new FormData();
data.append('hello', 'world');
let result = navigator.sendBeacon('./src', data);
if (result) {
console.log('請求成功排隊 等待執行');
} else {
console.log('失敗');
}
瀏覽器支援:
瀏覽器支援:Edge:14+,Firefox:31+,Chrome:39+,Opera:26+,IE:不支援。
雖然現在瀏覽器對sendBeacon
的支援很好,我們對其做一下相容性處理也是有必要的:
if (navigator.sendBeacon) {
// Beacon 程式碼
} else {
// 回退到 XHR同步請求或者不做處理
}
web wroker中使用Beacon
因為Beacon
是掛在navigator
下面,而web worker也有navigator
,去找了一下,真的給我找到了。
這兒有一個MDN提供的栗子),可以點進去看一下。
PS:對web worker不熟悉的同學可以看我這篇文章
Beacon其他相關
- 客戶端優化:可以將 Beacon 請求合併到其他請求上,一同處理, 尤其在移動環境下。
- Beacon更多的情況是用於做前端埋點,監控使用者活動,它的初衷也基於此。
小結
本文總共講了三個API,beforeunload
、unload
和Beacon
,Beacon
這個API估計知道的人比較少,以後遇到前端埋點和頁面解除安裝前傳送請求的需求,記得使用這三個API。
以上2019.02.19
參考資料: