1. 書接上回
【JS 】SharedWorker 優(yōu)化前端輪詢請(qǐng)求
經(jīng)過一頓改造签财,性能是上去了妒蔚,但是代碼卻還是不夠簡潔挟炬,所以繼續(xù)封裝
2. 思路
目標(biāo):使用一個(gè)js
文件完成所有輪詢請(qǐng)求,封裝調(diào)用方法鸥咖,簡化代碼
一個(gè)
js
文件判斷是web
環(huán)境還是work
環(huán)境web
環(huán)境返回封裝的函數(shù):判斷兼容性,創(chuàng)建worker
兄世,傳入?yún)?shù)啼辣,訂閱廣播work
環(huán)境訪問指定的webapi
并發(fā)送廣播
3. 代碼
3.1. 一個(gè)js
文件判斷是web
環(huán)境還是work
環(huán)境
判斷當(dāng)前環(huán)境的this
是window
還是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代碼
5. 頁面代碼變化
封裝之后砰蠢,頁面代碼較原始版本幾乎沒有增長
而且可以快速復(fù)用于其他API接口的輪詢操作