vue:sharedWorker共享工作者線程的使用

一.使用場景

某項目某接口撩荣,請求返回的數(shù)據(jù)量較大椭更,消耗服務(wù)器資源嚴重哪审。如果在web端打開多個頁簽,同時請求該接口虑瀑,會造成服務(wù)端負荷過重湿滓。這時畏腕,共享工作者線程(SharedWorker)就派上了用場。根據(jù)SharedWorker的特性茉稠,第一個頁面打開線程描馅,就會創(chuàng)建一個SharedWorker,第二個頁面也打開而线,就會使用已經(jīng)創(chuàng)建的同一個線程铭污。如此,只要在線程中將數(shù)據(jù)進行一定的緩存處理膀篮,多個頁簽打開時嘹狞,就可以共用這一份數(shù)據(jù)。

二.SharedWorker的一些特性

首先誓竿,SharedWorker的機制在不同瀏覽器的實現(xiàn)方式可能是不一樣的磅网,所以多頁面共享線程只限于同一瀏覽器,而不能跨瀏覽器筷屡。chrome打開的頁面只能與chrome頁面共享涧偷,而不能與火狐瀏覽器共享。

如果使用火狐瀏覽器毙死,可以直接在控制臺查看ShareWorker中打印的信息燎潮。但如果是Chrome瀏覽器或者sougou瀏覽器,ShareWorker文件中打印的信息在主線程頁面是無法查看的扼倘。Chrome瀏覽器查看SharedWorker的方式是在瀏覽器地址欄訪問 Chrome://inspect => SharedWorker=>inspect确封, sougou的查看方式請自行搜索。

關(guān)于SharedWorker的瀏覽器兼容性再菊,可查詢MDN相關(guān)文檔爪喘。https://developer.mozilla.org/zh-CN/docs/Web/API/SharedWorker

三.SharedWorker的引入與一般使用

如果在傳統(tǒng)的js頁面,可以通過new SharedWorker(src)創(chuàng)建線程

var myWorker = new SharedWorker("worker.js");

如果用vite + vue3技術(shù)棧纠拔,需要這樣引入

import sharedWorker from './worker?sharedworker';

在vite的開發(fā)模式下秉剑,它實際生成的是這樣一個文件。其中type類型為module绿语,則允許在開發(fā)的使用import 秃症,export的es6模塊語法。但在生產(chǎn)構(gòu)建后吕粹,worker文件會按照引入鏈會被構(gòu)建成一個文件种柑。

export default function WorkerWrapper() { return new Worker("/apps/home/worker.ts?worker_file", { "type": "module" }) }

需要注意的是,worker內(nèi)部并沒有window對象匹耕,因此如果你所引入的包(例如axios登)中包含window的引用聚请,構(gòu)建后在運行時大概率會報錯。我通過在worker文件開頭增加了兩行代碼修復(fù)了這個問題。所以能夠這樣做驶赏,是因為self是window的一個子集炸卑,它具備了大部分window的對象。

self.window = self;
window = self;

以下是常用方法

worker.js

// 與專用工作者線程不同煤傍,共享線程通過port來使用盖文,port是一個MessagePort對象,包含了onmessage,postMessage等方法蚯姆。
// 連接后通過port操作
self.onconnect = ({ ports }) => {
  ports.forEach((port) => {
    port.onmessage = (ev) => {
        const someData = {}
        port.postMessage(someData);
    };
    port.onmessageerror = (ev) => {
      console.log('error', ev);
    };
  });
};

四.進一步對調(diào)用方法進行封裝

由于SharedWorker是用postMessage發(fā)送消息五续,用onmessage接受消息,在使用是不如調(diào)用一個方法那么直接方便龄恋「砑荩可以考慮將其封裝成返回Promise方法。

import sharedWorker from './worker?sharedworker';
import { useUserStore } from '/@/store/modules/user';
export function useWorker() {
  const userStore = useUserStore();
  const worker = new sharedWorker();
  const onMessageResolveMap = {};
  const onMessageRejectMap = {};
  worker.port.onmessage = ({ data }) => {
    if (typeof onMessageResolveMap[data.funcName] === 'function') {
      if (data.data.statusCode === 200) {
        onMessageResolveMap[data.funcName](data.data?.data);
      } else {
        onMessageRejectMap[data.funcName](data.data.descript);
      }
    }
  };
  function getWorkerData(funcName, params, cacheTime = 2 * 60 * 1000) {
    return new Promise((resolve, reject) => {
      onMessageResolveMap[funcName] = resolve;
      onMessageRejectMap[funcName] = reject;
      worker.port.postMessage({ token: userStore.getToken, funcName, params, cacheTime });
    });
  }
  function stopWorker() {
    if (worker?.port) {
      worker?.port?.close();
    }
  }
  return {
    getWorkerData,
    stopWorker,
  };
}

如上getWorkerData方法郭毕,當方法調(diào)用時它碎,返回一個promise對象,但promise對象不會馬上變?yōu)閞esolve狀態(tài)显押,而是將promise的resolve和reject 先保存起來扳肛,在onmessage中在通過方法名返回響應(yīng)的數(shù)據(jù)。當然煮落,這個方法有一個潛在的問題敞峭,如果只用方法名做區(qū)分,當調(diào)用一個方法還沒返回數(shù)據(jù)接著又調(diào)一次蝉仇,resolve就會被覆蓋掉,它只能等返回數(shù)據(jù)再進行下一次調(diào)用殖蚕。解決的辦法也很簡單轿衔,只要在傳遞的參數(shù)中增加一個隨機數(shù)作為識別即可。

下面是在worker中的處理

import * as api from './api/api';
import { handleApi } from './api/utils';
self.window = self;
window = self;
const cacheApi = handleApi(api);
self.onconnect = ({ ports }) => {
  ports.forEach((port) => {
    port.onmessage = (ev) => {
      const { token, funcName, params, cacheTime } = ev.data;
      cacheApi[funcName](token, params, cacheTime).then((res) => {
        if (res?.data) {
          port.postMessage({ funcName, data: res.data });
        }
      });
    };
    port.onmessageerror = (ev) => {
      console.log('error', ev);
    };
  });
};

cacheApi是一個包含需要調(diào)用的所有接口方法的對象睦疫,通過主線程傳來的函數(shù)名funcName調(diào)用相關(guān)接口害驹。獲得數(shù)據(jù)后通過postMessage將數(shù)據(jù)返回給主線程。

如果想對接口數(shù)據(jù)根據(jù)cacheTime設(shè)置的時間進行緩存蛤育,需要怎么處理呢宛官?這里我用到了閉包的結(jié)構(gòu)。api這變量包含了api文件中定義的所有接口瓦糕。通過handleApi這個函數(shù)進行處理底洗,返回一系列內(nèi)部包含了緩存變量的閉包方法。

export function handleApi(apis) {
  const apiMap = {};
  for (const name in apis) {
    const originFunc = apis[name];
    const func = (() => {
      const pendings: any = {};
      // 根據(jù)不同的條件保存結(jié)果
      const results: any = {};
      const timeStamps: any = {};
      function waitResult(resolve, resultKey) {
        if (!results[resultKey]) {
          setTimeout(() => {
            waitResult(resolve, resultKey);
          }, 300);
        } else {
          resolve(results[resultKey]);
        }
      }
      function cacheValid(ret, resultKey, cacheTime) {
        const _timeStamp = new Date().getTime();
        // 檢查是否過了緩存時間
        if (_timeStamp - timeStamps[resultKey] > cacheTime || !ret) {
          return false;
        } else {
          return true;
        }
      }
      return (token, params, cacheTime) => {
        const resultKey = JSON.stringify(params);
        // 正在請求數(shù)據(jù)咕娄,等待
        if (pendings[resultKey]) {
          return new Promise((resolve) => {
            waitResult(resolve, resultKey);
          });
         // 緩存是否還生效
        } else if (cacheValid(results[resultKey], resultKey, cacheTime)) {
          return new Promise((resolve) => {
            resolve(results[resultKey]);
          });
        } else {
          // 開始一個新的請求
          pendings[resultKey] = true;
          return new Promise((resolve) => {
            originFunc(params, {
              headers: {
                Authorization: token,
              },
            }).then((res) => {
              if (res?.data?.statusCode === 200) {
                timeStamps[resultKey] = new Date().getTime();
                results[resultKey] = res;
              }
              pendings[resultKey] = false;
              resolve(results[resultKey]);
            });
          });
        }
      };
    })();
    apiMap[name] = func;
  }
  return apiMap;
}

這里面有幾個關(guān)鍵點:

如果有兩個頁面在同時請求一個接口亥揖,并且條件都一樣,后請求的不應(yīng)當再請求一次,而應(yīng)當?shù)却彺鏀?shù)據(jù)的到來费变。

因此摧扇,當?shù)谝粋€頁面在開始請求接口之前,就設(shè)置pending 為true, 這樣第二個頁面進入方法后挚歧,得知緩存中無數(shù)據(jù)并且接口在請求扛稽,會不斷調(diào)用自身得waitResult方法進行等待。

當接口返回數(shù)據(jù)后滑负,pending為false, 等待結(jié)束在张,返回緩存的數(shù)據(jù)。

過期時間的處理:cacheTime通過方法參數(shù)傳入橙困,因此不同的方法在調(diào)用時可以設(shè)置不同的過期時間瞧掺。當接口返回數(shù)據(jù)后,將當前時間戳記錄在timeStamps中凡傅,下次調(diào)同一個接口辟狈,則通過cacheValid方法檢查是否超過這個時間。為了保證數(shù)據(jù)優(yōu)先夏跷,如果上一次沒緩存到正確的數(shù)據(jù)哼转,也應(yīng)當作為過期情況重新調(diào)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末槽华,一起剝皮案震驚了整個濱河市壹蔓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猫态,老刑警劉巖佣蓉,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亲雪,居然都是意外死亡勇凭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門义辕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虾标,“玉大人,你說我怎么就攤上這事灌砖¤岛” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵基显,是天一觀的道長蘸吓。 經(jīng)常有香客問我,道長续镇,這世上最難降的妖魔是什么美澳? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上制跟,老公的妹妹穿的比我還像新娘舅桩。我一直安慰自己,他們只是感情好雨膨,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布擂涛。 她就那樣靜靜地躺著,像睡著了一般聊记。 火紅的嫁衣襯著肌膚如雪撒妈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天排监,我揣著相機與錄音狰右,去河邊找鬼。 笑死舆床,一個胖子當著我的面吹牛棋蚌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挨队,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼谷暮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盛垦?” 一聲冷哼從身側(cè)響起湿弦,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腾夯,沒想到半個月后颊埃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蝶俱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年竟秫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跷乐。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趾浅,靈堂內(nèi)的尸體忽然破棺而出愕提,到底是詐尸還是另有隱情,我是刑警寧澤皿哨,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布浅侨,位于F島的核電站,受9級特大地震影響证膨,放射性物質(zhì)發(fā)生泄漏如输。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望不见。 院中可真熱鬧澳化,春花似錦、人聲如沸稳吮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灶似。三九已至列林,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酪惭,已是汗流浹背希痴。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留春感,地道東北人砌创。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像甥厦,于是被迫代替她去往敵國和親纺铭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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