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

1. 書接上回

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

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

2. 思路

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

  1. 一個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. 一個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) 時需要傳入js文件路徑,所以要獲取當(dāng)前js的路徑備用

<script src="~/Scripts/SharedIntervalRequest.js"></script>
// 加載js文件時執(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. 封裝方法 setPostIntervalsetGetInterval

/**
 * 發(fā)送請求
 * @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è)置輪詢請求
 * @param {String} command 如:"POST http://domain.com/api"
 * @param {Function} callback 請求完成后的回調(diào)函數(shù)
 * @param {Number|null} [timeout] 輪詢間隔時間
 */
function setHttpInterval(command, callback, timeout) {
    if (typeof scope.SharedWorker === "undefined") {
        // 不支持 SharedWorker 時使用 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);  // 將請求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ù)可取消

如果要實現(xiàn)這個效果,就必須要重寫 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ā)送請求
     * @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è)置輪詢請求
     * @param {String} command 如:"POST http://domain.com/api"
     * @param {Function} callback 請求完成后的回調(diào)函數(shù)
     * @param {Number|null} [timeout] 輪詢間隔時間
     */
    function setHttpInterval(command, callback, timeout) {
        if (typeof scope.SharedWorker === "undefined") {
            // 不支持 SharedWorker 時使用 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. 啟動work

回顧代碼:
const worker = new SharedWorker(jsurl, command); // 將請求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ā)起方主動關(guān)閉,所以需要自己實現(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)自己管理了連接端口,所以就可以直接點對點發(fā)到所有端口,不再使用廣播對象 BroadcastChannel

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

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

3.3.5. 處理超時時間

let timer;
let minimumTimeout = null;
const connectionPorts = new Set();
// 重設(shè)輪詢
function resetInterval(timeout) {
    clearInterval(timer);
    // 獲取 minimumTimeout 與 timeout 中大于1000的較小的一個
    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ā)送請求
     * @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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容