Web Beacon 刷新/關閉頁面以前發送請求

背景:

有一個任務很是耗時會消耗後臺大量算力,因此在退出頁面的時候,要求前端這邊發送一個請求來殺死任務前端

一開始覺得這個需求很是簡單,就是在進入其餘路由前,發送一下請求,殺死一下任務就行了。ios

然而現實狠狠的打了個人臉,由於退出頁面的場景不止切換路由~git

退出頁面場景:

  1. 還在本網站,跳到其餘路由
  2. 刷新頁面/關閉頁面也須要發送請求來殺死任務

還在本網站,跳到其餘路由

這個比較簡單,在Vue中能夠經過路由離開的鉤子beforeRouteLeave來實現:github

beforeRouteLeave(to, from, next) {
    if (任務運行中) {
        // 發送請求
    }else{
        next(true) // 用戶離開
    }
 }
複製代碼

刷新頁面/關閉頁面的狀況:web

然而在刷新頁面的時候,beforeRouteLeave並不會執行,接着想到了下面這兩個API.chrome

beforeunloadunload

beforeunload 當瀏覽器窗口關閉或者刷新時觸發:

介紹axios

使用這個API能夠阻止頁面直接關閉,用戶經過點擊肯定/取消按鈕,來決定是否不關閉/刷新當前頁面。api

在 chrome 下長這個樣子,大家確定都見過:瀏覽器

如何使用安全

這個 API 的使用很是簡單,只要在頁面加載的時候監聽一下此事件,在須要出現彈窗的時候return 一個能夠轉化爲 true 的值,就能夠了。

// 頁面卸載以前
let killTask = false; // 是否殺死任務
window.onbeforeunload = e => {
  if (任務運行 && 對應頁面) {
    killTask = true;
    return '您可能有數據沒有保存'; // 在部分瀏覽器能夠修改彈窗標題
  } else {
    killTask = false;
  }
  // 沒有return一個能夠轉化爲true的值 就不會出現彈窗
};
複製代碼

出現此彈窗的瀏覽器行爲

如下行爲是基於 chorme:

  1. 焦點:你沒有點擊取消/肯定以前,焦點會一直在此彈窗上

  2. 你沒法在出現彈窗的頁面上執行任何操做

  3. 在其餘頁面也只能執行簡單的點擊操做,彈窗仍是存在頁面中間,沒法使用鍵盤,

  4. 鍵盤:鍵盤被綁定在彈窗上,只能經過按鍵EscEnter來執行取消/肯定操做

  5. 彈窗不是頁面的 dom,是瀏覽器的行爲

  6. 用戶取消/肯定,沒有回調 API,沒法得知

彈窗標題

chrome 中刷新頁面的標題:從新加載此網站?

chrome 中關閉頁面的標題:離開此網站?

如今大部分瀏覽器都不容許修改彈窗的標題,這個是爲了安全考慮,來保證用戶不受到錯誤信息的誤導,

迷茫

一開始我覺得既然能夠攔截到用戶的刷新/關閉頁面的操做,出現了上面那個彈窗,這個需求就已經作完了的時候。

而後發現,瀏覽器居然沒有提供用戶點擊肯定/取消刷新頁面的回調

到這裏我陷入了迷茫,盯着beforeunload這個 API 思考了起了人生的意義(實際上是在發呆),盯着盯着,從beforeunloadbefore我也就想到了unload這個 API。

瞬間又燃起了鬥志,何不試試這個unload

unload當頁面正在被卸載的時候觸發該事件

介紹

當頁面正在被卸載的時候觸發該事件,該事件不可取消,爲不可逆操做。

使用

直接監聽該事件就能夠了。

window.onunload = e => {}
複製代碼

結合需求:

killTaskbeforeunload時定義的變量,每次進入回調,都會給killTask賦值,使用這個值就能夠判斷何時能夠發送請求殺死任務。

window.onunload = e => {
  if (killTask && 對應頁面) {
    // 發送請求
  }
};
複製代碼

到這裏你們確定覺得已經作出來了該需求,事實上,並無!

沒法發送異步請求

我使用的是axios來發送請求,請求發出去了,可是被取消了,服務器那邊根本沒有收到請求,以下。

通過一頓分析:發現是axios請求是異步的問題,谷歌以後發現axios不支持同步的請求

最後使用原生的XMLHttpRequest對象,讓請求同步

大功告成! 實際上,上面纔是我第一次要發的內容,而下面更好的解決方法!

缺陷與更好的建議:

當我把這篇文章發佈在公衆號上,被奇舞週刊轉載了,上面一些大佬給我提了一些建議。

研究了一下,結果...好吧!我認可我是菜雞。

hey~ 不過這正是我寫博客的收穫之一,分享經驗,收穫知識!

性能缺陷:

XHR同步請求會阻礙頁面卸載,若是是刷新/跳轉頁面的話,頁面從新展現速度會變慢,致使性能問題

畢竟向網絡發送請求並得到響應可能會超級慢,有多是用戶網絡環境比較差,又或者是服務器掛了,請求一直沒返回回來...

基於性能問題,大佬們推薦使用Beacon代替XHR,而後通過一番搜索...

Beacon API

  1. Beacon API用於將少許數據經過post請求發送到服務器
  2. Beacon是非阻塞請求,不須要響應

完美解決性能缺陷問題:

  1. 瀏覽器將 Beacon 請求排隊讓它在空閒的時候執行並當即返回控制
  2. 它在unload狀態下也能夠異步發送,不阻塞頁面刷新/跳轉等操做。

因此**Beacon能夠完美解決上面提到的因XHR同步請求阻塞而引發的性能缺陷問題**。

使用:navigator.sendBeacon()

完整API

let result = navigator.sendBeacon(url, data);
複製代碼

Beacon是掛在navigator下面的,上面就是它的完整API。

result是一個布爾值,表明此次發送請求的結果:

  • 若是瀏覽器接受而且把請求排隊了則返回 tru
  • 若是在這個過程當中出現了問題就返回 false

navigator.sendBeacon接受兩個參數:

  1. url: 請求的 URL。請求是 POST 請求。
  2. 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,beforeunloadunloadBeaconBeacon這個API估計知道的人比較少,之後遇到前端埋點和頁面卸載前發送請求的需求,記得使用這三個API。

以上2019.02.19

博客前端積累文檔公衆號GitHub、wx:OBkoro一、郵箱:obkoro1@foxmail.com

參考資料:

MDN

頁面跳轉時,統計數據丟失問題探討

使用 Web Beacon API 記錄活動

相關文章
相關標籤/搜索