可行,但worker不能访问navigator.connection或操作dom,需主线程调度资源并接收结果;worker用fetch并发测rtt(head请求)和带宽(get+arraybuffer),剔除异常值后取中位数或平均值。

直接用 Web Worker 测真实带宽和 RTT 是可行的,但要注意:Worker 本身不能访问 navigator.connection(该 API 主线程专属),也不能触发 DOM 事件或操作页面元素;它的作用是把耗时的网络探测任务从主线程剥离,避免阻塞 UI,同时配合主线程完成完整测速逻辑。
核心分工:主线程管权限与调度,Worker 管并发请求与计时
真实带宽和 RTT 都需要主动发起网络请求并测量时间差。Worker 可以发起 fetch/XHR、控制并发、记录毫秒级耗时,但必须由主线程提供测试资源 URL、启动指令,并接收结果。
- 主线程负责检查
navigator.onLine、读取navigator.connection.downlink(如有)、准备测试文件 URL(如 CDN 上固定大小的二进制 blob) - 主线程创建 Worker,并用
postMessage把 URL 和配置(如并发请求数、超时阈值)传过去 - Worker 内部用
fetch并发拉取多个小文件(例如 100KB 的 .bin),每个请求都用performance.now()打点:发起前、收到 headers 后、完全接收后 - RTT 取「请求发出 → 收到响应头」的时间差(避开 body 下载干扰);带宽则基于「完整下载量 ÷ 总耗时」,剔除异常值后取中位数
- Worker 计算完后,用
postMessage把 { rtt: xx, bandwidth: yy, count: n } 发回主线程
具体实现要点
不依赖第三方库,纯原生 HTML5 + JS:
-
测试文件要小而稳:选一个 50–200KB 的静态二进制文件(如
/speed-test-150k.bin),放在高可用 CDN,禁用缓存(cache: 'no-store') -
并发控制防误判:单次测 3–5 个请求并行,避免因单个 TCP 建连抖动导致结果偏差;Worker 中用
Promise.allSettled统一收口 -
RTT 测量更准的方式:用
fetch(..., { method: 'HEAD' }),只拿响应头,耗时≈纯网络往返,基本排除服务端处理和 body 传输影响 - 带宽计算需去噪:丢弃耗时最长/最短的 20% 请求,剩余取平均下载速度(单位 MB/s),再换算成 Mbps(×8)
-
超时必须设:每个请求配
signal: AbortController.timeout(8000),防止弱网卡死,Worker 不会因此崩溃
为什么不用 navigator.connection.downlink?
它返回的是浏览器“估算”的有效下行带宽(比如标为 effectiveType: '4g' 时 downlink ≈ 1.5 Mbps),不是实测值。这个值可能滞后、不准,尤其在 Wi-Fi 切换蜂窝、QoS 限速、后台应用占带宽时明显偏高。真实带宽必须靠主动下载+计时得出。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
一个精简的 Worker 示例逻辑(speed-test-worker.js)
(主线程调用时传入 { url: 'https://cdn.example.com/test.bin', concurrency: 4 })
self.onmessage = async function(e) {
const { url, concurrency = 4 } = e.data;
const controllers = Array.from({ length: concurrency }, () => new AbortController());
const promises = controllers.map(ctrl =>
fetch(url, {
method: 'HEAD',
cache: 'no-store',
signal: ctrl.signal
})
.then(res => ({
rtt: performance.now() - start,
ok: res.ok,
status: res.status
}))
.catch(err => ({ rtt: Infinity, ok: false, error: err.name }))
);
<p>const start = performance.now();
const results = await Promise.allSettled(promises);</p><p>const validRtts = results
.filter(r => r.status === 'fulfilled' && r.value.ok)
.map(r => r.value.rtt)
.filter(t => t > 0 && t </p><p>const rtt = validRtts.length ? Number(validRtts.reduce((a, b) => a + b, 0) / validRtts.length).toFixed(1) : 'N/A';</p><p>self.postMessage({ rtt, count: validRtts.length });
};</p>
带宽测试同理,换成 GET + response.arrayBuffer(),再按 size / duration 算速。整个过程不卡页面,用户可继续滚动或输入。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










