【JS 】SharedWorker 優(yōu)化前端輪詢請(qǐng)求(續(xù))

1. 書接上回

【JS 】SharedWorker 優(yōu)化前端輪詢請(qǐng)求

經(jīng)過一頓改造签财,性能是上去了妒蔚,但是代碼卻還是不夠簡潔挟炬,所以繼續(xù)封裝

2. 思路

目標(biāo):使用一個(gè)js文件完成所有輪詢請(qǐng)求,封裝調(diào)用方法鸥咖,簡化代碼

  1. 一個(gè)js文件判斷是web環(huán)境還是work環(huán)境

  2. web環(huán)境返回封裝的函數(shù):判斷兼容性,創(chuàng)建worker兄世,傳入?yún)?shù)啼辣,訂閱廣播

  3. work環(huán)境訪問指定的webapi并發(fā)送廣播

3. 代碼

3.1. 一個(gè)js文件判斷是web環(huán)境還是work環(huán)境

判斷當(dāng)前環(huán)境的thiswindow還是SharedWorkerGlobalScope

(function(scope){
    // 這里簡化一下 只要不是 window 就認(rèn)為是 work
    const isWorker = !(scope.constructor && scope.constructor.name === "Window");
    // ...
})(this)

3.2. web環(huán)境返回封裝的函數(shù):判斷兼容性,創(chuàng)建worker御滩,傳入?yún)?shù)鸥拧,訂閱廣播

3.2.1. 獲取當(dāng)前js文件路徑

由于在創(chuàng)建 new SharedWorker(aURL, name) 時(shí)需要傳入js文件路徑党远,所以要獲取當(dāng)前js的路徑備用

<script src="~/Scripts/SharedIntervalRequest.js"></script>
// 加載js文件時(shí)執(zhí)行這段代碼,可以獲取當(dāng)前js文件的完整路徑
const jsurl = (() => {
    try {
        return window.document.currentScript.src || null.toString();
    } catch (e) {
        return /(?:http|https|file):\/\/.*?\/.+?.js/.exec(e.stack || e.sourceURL || e.stacktrace)[0];
    }
})();
// jsurl = 'http://localhost:12345/Scripts/SharedIntervalRequest.js'

3.2.2. 封裝方法 setPostInterval富弦,setGetInterval

/**
 * 發(fā)送請(qǐng)求
 * @param {String} command 如:"POST http://domain.com/api"
 */
function request(command) {
    const method = command.split(' ')[0];
    const path = command.substring(method.length + 1);
    return fetch(path, { method }).then(r => r.text())
}
/**
 * 設(shè)置輪詢請(qǐng)求
 * @param {String} command 如:"POST http://domain.com/api"
 * @param {Function} callback 請(qǐng)求完成后的回調(diào)函數(shù)
 * @param {Number|null} [timeout] 輪詢間隔時(shí)間
 */
function setHttpInterval(command, callback, timeout) {
    if (typeof scope.SharedWorker === "undefined") {
        // 不支持 SharedWorker 時(shí)使用 setInterval
        request(command).then(callback);
        return setInterval(() => request(command).then(callback), timeout);
    }

    // 開啟共享任務(wù) ...
}

window.setPostInterval = (apipath, callback, timeout) => setHttpInterval("POST " + apipath, callback, timeout);
window.setGetInterval = (apipath, callback, timeout) => setHttpInterval("GET " + apipath, callback, timeout);

3.2.3. Work傳參沟娱,close指令

const worker = new SharedWorker(jsurl, command);  // 將請(qǐng)求command作為name傳給work
worker.port.start();
// 發(fā)送消息傳遞 timeout 參數(shù)
worker.port.postMessage({ type: "timeout", timeout });
worker.port.onmessage = msg => callback(msg.data);
// 發(fā)送消息執(zhí)行 close 指令
const close = () => worker.port.postMessage({ type: "close" }) || worker.port.close();
window.addEventListener("beforeunload", close);
return close;

3.2.4. 任務(wù)可取消

如果要實(shí)現(xiàn)這個(gè)效果,就必須要重寫 clearInterval
const timer = setPostInterval("/Admin/Dashboard/GetAppRelaseNotice", callback, 1000 * 60);
clearInterval(timer);

if (typeof window.__clearInterval__ === "undefined") {
    window.__clearInterval__ = scope.clearInterval;
    window.clearInterval = function (id) {
        if (id instanceof Function) {
            id();
        } else {
            scope.__clearInterval__.apply(window, arguments);
        }
    }
}

3.2.5. 完整web環(huán)境代碼

(function (scope) {
    const isWorker = !(scope.constructor && scope.constructor.name === "Window");

    /**
     * 發(fā)送請(qǐng)求
     * @param {String} command 如:"POST http://domain.com/api"
     */
    function request(command) {
        const method = command.split(' ')[0];
        const path = command.substring(method.length + 1);
        return fetch(path, { method }).then(r => r.text())
    }

    if (isWorker) {
        // ... WORK 部分代碼 ...
        return; 
    }

    const jsurl = (() => {
        try {
            return scope.document.currentScript.src || null.toString();
        } catch (e) {
            return /(?:http|https|file):\/\/.*?\/.+?.js/.exec(e.stack || e.sourceURL || e.stacktrace)[0];
        }
    })();
    if (!jsurl) {
        throw Error("獲取js文件路徑失敗");
    }

    /**
     * 設(shè)置輪詢請(qǐng)求
     * @param {String} command 如:"POST http://domain.com/api"
     * @param {Function} callback 請(qǐng)求完成后的回調(diào)函數(shù)
     * @param {Number|null} [timeout] 輪詢間隔時(shí)間
     */
    function setHttpInterval(command, callback, timeout) {
        if (typeof scope.SharedWorker === "undefined") {
            // 不支持 SharedWorker 時(shí)使用 setInterval
            request(command).then(callback);
            return setInterval(() => request(command).then(callback), timeout);
        }

        // 開啟共享任務(wù)
        const worker = new SharedWorker(jsurl, command);
        worker.port.start();
        worker.port.postMessage({ type: "timeout", timeout });
        worker.port.onmessage = msg => callback(msg.data);
        const close = () => worker.port.postMessage({ type: "close" }) || worker.port.close();
        scope.addEventListener("beforeunload", close);
        return close;
    }

    if (typeof scope.__clearInterval__ === "undefined") {
        scope.__clearInterval__ = scope.clearInterval;
        scope.clearInterval = function (id) {
            if (id instanceof Function) {
                id();
            } else {
                scope.__clearInterval__.apply(scope, arguments);
            }
        }
    }

    scope.setPostInterval = (apipath, callback, timeout) => setHttpInterval("POST " + apipath, callback, timeout);

    scope.setGetInterval = (apipath, callback, timeout) => setHttpInterval("GET " + apipath, callback, timeout);

})(this);

3.3. work環(huán)境訪問指定的webapi并發(fā)送廣播

3.3.1. 啟動(dòng)work

回顧代碼:
const worker = new SharedWorker(jsurl, command); // 將請(qǐng)求command作為name傳給work

const command = this.name; // 將 name 還原為 command
let timer;
onconnect = function (e) {
    request(command).then(x => broadcast(x);
    clearInterval(timer);
    timer = setInterval(() => request(command).then(x => broadcast(x)), 60000);
}

3.3.2. 關(guān)閉work

SharedWorker 無法由發(fā)起方主動(dòng)關(guān)閉腕柜,所以需要自己實(shí)現(xiàn)管理連接的方法

回顧代碼:
const close = () => worker.port.postMessage({ type: "close" }) || worker.port.close();
window.addEventListener("beforeunload", close);

// 管理接連端口
const connectionPorts = new Set();

scope.onconnect = function (e) {
    const port = e.ports[0];
    connectionPorts.add(port); // 加入端口
    port.onmessage = msg => {
        if(msg.data.type === "close"){
            connectionPorts.delete(port);  // 處理 close 指令
        }
    }
};

3.3.3. 處理指令和傳參

回顧代碼:
worker.port.postMessage({ type: "timeout", timeout: 60*1000 });
worker.port.postMessage({ type: "close" })

// 定義指令處理程序
const handlers = {
    timeout(port, value) { ... }
    close(port) { connectionPorts.delete(port); }
};

// 訂閱消息處理程序
scope.onconnect = function (e) {
    const port = e.ports[0];
    port.onmessage = msg => {
        const handler = handlers[msg.data && msg.data.type && msg.data.type.toLowerCase()];
        if (handler) {
            handler(port, msg.data.value, msg.data);
        }
    };
}

3.3.4. 處理廣播

由于已經(jīng)自己管理了連接端口济似,所以就可以直接點(diǎn)對(duì)點(diǎn)發(fā)到所有端口,不再使用廣播對(duì)象 BroadcastChannel

function broadcast(msg) {
    connectionPorts.forEach(port => port.postMessage(msg))
}

回顧代碼:訂閱
worker.port.onmessage = msg => callback(msg.data);

3.3.5. 處理超時(shí)時(shí)間

let timer;
let minimumTimeout = null;
const connectionPorts = new Set();
// 重設(shè)輪詢
function resetInterval(timeout) {
    clearInterval(timer);
    // 獲取 minimumTimeout 與 timeout 中大于1000的較小的一個(gè)
    minimumTimeout = [minimumTimeout, timeout].filter(x => x >= 1000).sort()[0];
    // 如果 connectionPorts 中已經(jīng)沒有連接端口了盏缤,則不再執(zhí)行 request 函數(shù)
    timer = setInterval(() => connectionPorts.size && request(command).then(broadcast), minimumTimeout || 60000);
}

3.3.6. 完整work環(huán)境代碼

(function (scope) {
    const isWorker = !(scope.constructor && scope.constructor.name === "Window");

    /**
     * 發(fā)送請(qǐng)求
     * @param {String} command 如:"POST http://domain.com/api"
     */
    function request(command) {
        const method = command.split(' ')[0];
        const path = command.substring(method.length + 1);
        return fetch(path, { method }).then(r => r.text())
    }

    if (isWorker) {
        // 作為Work
        const command = scope.name;
        let timer;
        let minimumTimeout = null;
        const connectionPorts = new Set();
        function resetInterval(timeout) {
            clearInterval(timer);
            minimumTimeout = [minimumTimeout, timeout].filter(x => x >= 1000).sort()[0];
            timer = setInterval(() => connectionPorts.size && request(command).then(broadcast), minimumTimeout || 60000);
        }

        function broadcast(msg) {
            connectionPorts.forEach(port => port.postMessage(msg))
        }

        const handlers = {
            timeout(_, data) {
                resetInterval(data.timeout);
            },
            close(port) {
                connectionPorts.delete(port);
            }
        };

        scope.onconnect = function (e) {
            const port = e.ports[0];
            connectionPorts.add(port);

            request(command).then(broadcast);
            resetInterval(minimumTimeout);

            port.onmessage = msg => {
                const handler = handlers[msg.data && msg.data.type && msg.data.type.toLowerCase()];
                if (handler) {
                    handler(port, msg.data);
                }
            };
        }
        return;
    }

    // ... web 環(huán)境代碼 ...
})(this);

4. 完整work.js代碼

work.js

5. 頁面代碼變化

封裝之后砰蠢,頁面代碼較原始版本幾乎沒有增長
而且可以快速復(fù)用于其他API接口的輪詢操作

6. Demo

SharedWorker 封裝演示 - JSRUN

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市唉铜,隨后出現(xiàn)的幾起案子台舱,更是在濱河造成了極大的恐慌,老刑警劉巖打毛,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柿赊,死亡現(xiàn)場離奇詭異,居然都是意外死亡幻枉,警方通過查閱死者的電腦和手機(jī)碰声,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熬甫,“玉大人胰挑,你說我怎么就攤上這事〈患纾” “怎么了瞻颂?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長郑象。 經(jīng)常有香客問我贡这,道長,這世上最難降的妖魔是什么厂榛? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任盖矫,我火速辦了婚禮,結(jié)果婚禮上击奶,老公的妹妹穿的比我還像新娘辈双。我一直安慰自己,他們只是感情好柜砾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布湃望。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪证芭。 梳的紋絲不亂的頭發(fā)上瞳浦,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音檩帐,去河邊找鬼术幔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛湃密,可吹牛的內(nèi)容都是我干的诅挑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼泛源,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼拔妥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起达箍,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤没龙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缎玫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硬纤,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年赃磨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筝家。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邻辉,死狀恐怖溪王,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情值骇,我是刑警寧澤莹菱,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吱瘩,受9級(jí)特大地震影響道伟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜使碾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一皱卓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧部逮,春花似錦、人聲如沸嫂易。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颅和,卻和暖如春傅事,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峡扩。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工蹭越, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人教届。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓响鹃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親案训。 傳聞我的和親對(duì)象是個(gè)殘疾皇子买置,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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