【JS 】SharedWorker 优化前端轮询请求(续)
1. 书接上回
经过一顿改造,性能是上去了,但是代码却还是不够简洁,所以继续封装
2. 思路
目标:使用一个js
文件完成所有轮询请求,封装调用方法,简化代码
-
一个
js
文件判断是web
环境还是work
环境 -
web
环境返回封装的函数:判断兼容性,创建worker
,传入参数,订阅广播 -
work
环境访问指定的webapi
并发送广播
3. 代码
3.1. 一个js
文件判断是web
环境还是work
环境
判断当前环境的this
是window
还是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. 封装方法 setPostInterval
,setGetInterval
/** * 发送请求 * @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代码
5. 页面代码变化
封装之后,页面代码较原始版本几乎没有增长
而且可以快速复用于其他API接口的轮询操作
6. Demo
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库