【JS 】SharedWorker 优化前端轮询请求(续)

1. 书接上回

【JS 】SharedWorker 优化前端轮询请求

经过一顿改造,性能是上去了,但是代码却还是不够简洁,所以继续封装

2. 思路

目标:使用一个js文件完成所有轮询请求,封装调用方法,简化代码

  1. 一个js文件判断是web环境还是work环境

  2. web环境返回封装的函数:判断兼容性,创建worker,传入参数,订阅广播

  3. work环境访问指定的webapi并发送广播

3. 代码

3.1. 一个js文件判断是web环境还是work环境

判断当前环境的thiswindow还是SharedWorkerGlobalScope

(function(scope){
// 这里简化一下 只要不是 window 就认为是 work
const isWorker = !(scope.constructor && scope.constructor.name === "Window");
// ...
})(this)

3.2. web环境返回封装的函数:判断兼容性,创建worker,传入参数,订阅广播

3.2.1. 获取当前js文件路径

由于在创建 new SharedWorker(aURL, name) 时需要传入js文件路径,所以要获取当前js的路径备用

<script src="~/Scripts/SharedIntervalRequest.js"></script>
// 加载js文件时执行这段代码,可以获取当前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

/**
* 发送请求
* @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())
}
/**
* 设置轮询请求
* @param {String} command 如:"POST http://domain.com/api"
* @param {Function} callback 请求完成后的回调函数
* @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);
}
// 开启共享任务 ...
}
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();
// 发送消息传递 timeout 参数
worker.port.postMessage({ type: "timeout", timeout });
worker.port.onmessage = msg => callback(msg.data);
// 发送消息执行 close 指令
const close = () => worker.port.postMessage({ type: "close" }) || worker.port.close();
window.addEventListener("beforeunload", close);
return close;

3.2.4. 任务可取消

如果要实现这个效果,就必须要重写 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环境代码

(function (scope) {
const isWorker = !(scope.constructor && scope.constructor.name === "Window");
/**
* 发送请求
* @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文件路径失败");
}
/**
* 设置轮询请求
* @param {String} command 如:"POST http://domain.com/api"
* @param {Function} callback 请求完成后的回调函数
* @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);
}
// 开启共享任务
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环境访问指定的webapi并发送广播

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. 关闭work

SharedWorker 无法由发起方主动关闭,所以需要自己实现管理连接的方法

回顾代码:
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. 处理广播

由于已经自己管理了连接端口,所以就可以直接点对点发到所有端口,不再使用广播对象 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();
// 重设轮询
function resetInterval(timeout) {
clearInterval(timer);
// 获取 minimumTimeout 与 timeout 中大于1000的较小的一个
minimumTimeout = [minimumTimeout, timeout].filter(x => x >= 1000).sort()[0];
// 如果 connectionPorts 中已经没有连接端口了,则不再执行 request 函数
timer = setInterval(() => connectionPorts.size && request(command).then(broadcast), minimumTimeout || 60000);
}

3.3.6. 完整work环境代码

(function (scope) {
const isWorker = !(scope.constructor && scope.constructor.name === "Window");
/**
* 发送请求
* @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 环境代码 ...
})(this);

4. 完整work.js代码

work.js

5. 页面代码变化

封装之后,页面代码较原始版本几乎没有增长
而且可以快速复用于其他API接口的轮询操作

6. Demo

SharedWorker 封装演示 - JSRUN

posted @   冰麟轻武  阅读(282)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示