HTML Web Worker 标签页恢复后批量发送请求异常问题排查
首先,咱们先明确你遇到的核心问题:浏览器标签页失活时,Web Worker的定时器会被节流/暂停,恢复活跃后会一次性触发所有积压的定时器回调,导致批量发送重复经纬度的请求。
问题根源
浏览器为了节省系统资源,当标签页处于非活跃状态时,会对setTimeout/setInterval这类定时器进行节流处理(比如强制把定时器最小间隔拉长到1秒以上,甚至直接暂停定时器)。你的Web Worker里用了setTimeout("timedCount()", ft)实现固定间隔触发,当标签页失活后,这些定时器会被挂起;一旦标签页恢复活跃,所有积压的定时器会立刻集中执行,导致Worker一次性post出多个sequence值(3、4、5...)。
而主线程的逻辑是收到Worker消息后才触发地理位置获取,此时浏览器会返回最近一次缓存的经纬度数据,所以所有批量触发的请求都会带上完全相同的位置信息,这就出现了你看到的异常情况。
另外还有几个潜在的小问题:
- Worker里使用字符串形式的
setTimeout("timedCount()", ft),这种写法会在全局作用域执行代码,存在安全风险且性能不佳 - 主线程里的
sequence、lat、lon等变量都是全局变量,容易引发命名冲突或意外修改 - 没有处理Ajax请求失败的场景,也没有对地理位置请求做容错处理
修复方案
1. 重构Web Worker逻辑,取消固定定时器,改为"按需触发"
把Worker改成被动响应模式:只有收到主线程的"继续"指令时,才生成下一个序列并发送给主线程,彻底避免定时器积压。
修改后的location_worker.js:
let sequence = 0; // 监听主线程的指令 self.addEventListener('message', (e) => { if (e.data === 'next') { sequence += 1; self.postMessage(sequence); console.log('当前序列:', sequence); } else if (e.data === 'stop') { self.close(); // 主动关闭Worker } });
2. 主线程改为"请求完成后通知Worker触发下一次"的模式
这样每一轮请求都必须等上一轮的地理位置获取、Ajax请求全部完成后,才会启动下一轮,完全不会产生积压请求,不管标签页是否活跃,请求都是按顺序逐个发起的。
修改后的main.html核心代码:
// 使用局部变量避免全局污染 if (typeof(Worker) !== "undefined") { let worker; let isTracking = false; // 初始化Worker并启动追踪 function initTracking() { if (!worker) { worker = new Worker("/location_worker.js"); worker.onmessage = function(event) { const sequence = event.data; // 检查是否已完成追踪 if (Cookies.get("location_tracked") === "done") { stopTracking(); return; } // 获取地理位置 if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( function(position) { const lat = position.coords.latitude; const lon = position.coords.longitude; // 发送Ajax请求 $.ajax({ type: "POST", url: "/api/", data: { 'action':'receive_loc', 'lat': lat, 'lon': lon, 'enc_data': enc_data, 'reached': reached, 'sequence' : sequence }, success: function(jres) { const res = JSON.parse(jres); if (res.status === 11) { stopTracking(); } else { // 请求成功,通知Worker触发下一次 worker.postMessage('next'); } }, error: function() { // 请求失败也可选择重试或继续,这里示例继续触发下一次 worker.postMessage('next'); } }); }, function(code, message) { $('#error').html("<b>Location Sharing blocked by User..</b>"); stopTracking(); }, { timeout: 10000 } // 添加位置获取超时设置,避免无限等待 ); } else { alert("Sorry, your browser does not support HTML5 geolocation."); stopTracking(); } }; // 触发第一次序列请求 worker.postMessage('next'); } } // 停止追踪的统一方法 function stopTracking() { if (worker) { worker.postMessage('stop'); worker.terminate(); worker = undefined; } isTracking = false; } // 启动追踪 if (!isTracking) { initTracking(); isTracking = true; } } else { document.getElementById("result").innerHTML = "Sorry, your browser does not support Web Workers..."; }
3. 额外优化建议
- 给
navigator.geolocation.getCurrentPosition添加maximumAge参数,控制位置缓存时间,比如{ maximumAge: 5000 },避免获取到太久之前的位置数据 - 可以给Ajax请求添加重试逻辑,提升稳定性
- 把所有全局变量都改成局部变量,减少意外冲突的风险
方案生效原因
原来的固定间隔定时器模式,本质是"不管上一轮请求是否完成,到点就触发下一次",很容易在标签页失活时积压任务。而改成"请求完成后触发下一次"的模式,每一轮请求都依赖上一轮的完成信号,完全不会产生积压,从根源上解决了标签页恢复活跃后批量发送重复请求的问题。
内容的提问来源于stack exchange,提问作者ɹɐqʞɐ zoɹǝɟ

