使用sendBeacon進行前端數(shù)據(jù)上報

前言

最近接到一個需求炭玫,需要統(tǒng)計頁面的相關數(shù)據(jù)吞加,并進行上報叶圃,本文就介紹一下數(shù)據(jù)上報的一些方法盗似。

上報數(shù)據(jù)的時機

  • 頁面加載時

此時進行數(shù)據(jù)上報,只需要在頁面 load 時上報即可。

window.addEventListener('load', reportData, false);
  • 頁面卸載或頁面刷新時

此時進行數(shù)據(jù)上報闽瓢,只需要在頁面 beforeunload 時上報即可心赶。

window.addEventListener('beforeunload', reportData, false);
  • SPA 路由切換時

    • 如果是 vue-routerreact-router@3 及以下版本,則可以在 hooks 里進行上報操作缨叫。
    • 如果是 react-router@4 則需要在 Routes 根組件的生命周期內(nèi)進行上報。
  • 頁面多個 tab 切換時

如果是這種情況耻姥,可以在 visibilitychange 時通過讀取 document.visibilityStatedocument.hidden 區(qū)分頁面 tab 的激活狀態(tài)琐簇,判斷是否需要進行上報蒸健。

document.addEventListener("visibilitychange", function() {
  if(document.visibilityState === 'visible') {
    reportData();
  }
  if(document.visibilityState === 'hidden') {
    reportData2();
  }
  // your code ...
});

上報數(shù)據(jù)的方法

1. 直接發(fā)請求上報

我們可以直接將數(shù)據(jù)通過 ajax 發(fā)送到后端似忧,以 axios 為例丈秩。

axios.post(url, data);

但這種方法有一個問題,就是在頁面卸載或刷新時進行上報的話饺著,請求可能會在瀏覽器關閉或重新加載前還未發(fā)送至服務端就被瀏覽器 cancel 掉,導致數(shù)據(jù)上報失敗瓶籽。

我們可以將 ajax 請求改為同步方法匠童,這樣就能保證請求一定能發(fā)送到服務端。由于 fetchaxios 都不支持同步請求塑顺,所以需要通過 XMLHttpRequest 發(fā)送同步請求汤求。

const syncReport = (url, { data = {}, headers = {} } = {}) => {
  const xhr = new XMLHttpRequest();
  xhr.open('POST', url, false);
  xhr.withCredentials = true;
  Object.keys(headers).forEach((key) => {
    xhr.setRequestHeader(key, headers[key]);
  });
  xhr.send(JSON.stringify(data));
};

這里要注意的是,將請求改為同步以后严拒,會阻塞頁面關閉或重新加載的過程扬绪,這樣就會影響用戶體驗。

2. 動態(tài)圖片

我們可以通過在 beforeunload 事件處理器中創(chuàng)建一個圖片元素并設置它的 src 屬性的方法來延遲卸載以保證數(shù)據(jù)的發(fā)送裤唠,因為絕大多數(shù)瀏覽器會延遲卸載以保證圖片的載入挤牛,所以數(shù)據(jù)可以在卸載事件中發(fā)送。

const reportData = (url, data) => {
  let img = document.createElement('img');
  const params = [];
  Object.keys(data).forEach((key) => {
    params.push(`${key}=${encodeURIComponent(data[key])}`);
  });
  img.onload = () => img = null;
  img.src = `${url}?${params.join('&')}`;
};

此時服務端可以返回一個 1px * 1px 的圖片种蘸,保證觸發(fā) imgonload 事件,但如果某些瀏覽器在實現(xiàn)上無法保證圖片的載入航瞭,就會導致上報數(shù)據(jù)的丟失诫硕。

3. sendBeacon

為了解決上述問題,便有了 navigator.sendBeacon 方法刊侯,使用該方法發(fā)送請求章办,可以保證數(shù)據(jù)有效送達,且不會阻塞頁面的卸載或加載滨彻,并且編碼比起上述方法更加簡單藕届。

用法如下:

navigator.sendBeacon(url, data);

url 就是上報地址,data 可以是 ArrayBufferView亭饵,Blob休偶,DOMStringFormdata,根據(jù)官方規(guī)范冬骚,需要 request header 為 CORS-safelisted-request-header椅贱,在這里則需要保證 Content-Type 為以下三種之一:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

我們一般會用到 DOMString , BlobFormdata 這三種對象作為數(shù)據(jù)發(fā)送到后端,下面以這三種方式為例進行說明只冻。

  • DOMString

如果數(shù)據(jù)類型是 string庇麦,則可以直接上報,此時該請求會自動設置請求頭的 Content-Typetext/plain喜德。

const reportData = (url, data) => {
  navigator.sendBeacon(url, data);
};
  • Blob

如果用 Blob 發(fā)送數(shù)據(jù)山橄,這時需要我們手動設置 Blob 的 MIME type,一般設置為 application/x-www-form-urlencoded舍悯。

const reportData = (url, data) => {
  const blob = new Blob([JSON.stringify(data), {
    type: 'application/x-www-form-urlencoded',
  }]);
  navigator.sendBeacon(url, blob);
};
  • Formdata

可以直接創(chuàng)建一個新的 Formdata航棱,此時該請求會自動設置請求頭的 Content-Typemultipart/form-data睡雇。

const reportData = (url, data) => {
  const formData = new FormData();
  Object.keys(data).forEach((key) => {
    let value = data[key];
    if (typeof value !== 'string') {
      // formData只能append string 或 Blob
      value = JSON.stringify(value);
    }
    formData.append(key, value);
  });
  navigator.sendBeacon(url, formData);
};

注意這里的 JSON.stringify 操作,服務端需要將數(shù)據(jù)進行 parse 才能得到正確的數(shù)據(jù)饮醇。

總結(jié)

我們可以使用 sendBeacon 發(fā)送數(shù)據(jù)它抱,這一方法既能保證數(shù)據(jù)可靠性,也不影響用戶體驗朴艰,如果瀏覽器不支持該方法观蓄,則可以降級使用同步的 ajax 發(fā)送數(shù)據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祠墅,一起剝皮案震驚了整個濱河市侮穿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毁嗦,老刑警劉巖亲茅,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異狗准,居然都是意外死亡克锣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門腔长,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娶耍,“玉大人,你說我怎么就攤上這事饼酿。” “怎么了胚膊?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵故俐,是天一觀的道長。 經(jīng)常有香客問我紊婉,道長药版,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任喻犁,我火速辦了婚禮槽片,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肢础。我一直安慰自己还栓,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布传轰。 她就那樣靜靜地躺著剩盒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慨蛙。 梳的紋絲不亂的頭發(fā)上辽聊,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天纪挎,我揣著相機與錄音,去河邊找鬼跟匆。 笑死异袄,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的玛臂。 我是一名探鬼主播烤蜕,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼垢揩!你這毒婦竟也來了玖绿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤叁巨,失蹤者是張志新(化名)和其女友劉穎斑匪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锋勺,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蚀瘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了庶橱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贮勃。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苏章,靈堂內(nèi)的尸體忽然破棺而出寂嘉,到底是詐尸還是另有隱情,我是刑警寧澤枫绅,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布泉孩,位于F島的核電站,受9級特大地震影響并淋,放射性物質(zhì)發(fā)生泄漏寓搬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一县耽、第九天 我趴在偏房一處隱蔽的房頂上張望句喷。 院中可真熱鬧,春花似錦兔毙、人聲如沸唾琼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽父叙。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趾唱,已是汗流浹背涌乳。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甜癞,地道東北人夕晓。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像悠咱,于是被迫代替她去往敵國和親蒸辆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容