浏览器非活动页定时器执行频率下降问题

本文介绍了一种使用WebWorker来解决浏览器后台标签中定时器精度降低问题的方法,该问题影响了在线客服系统的心跳机制稳定性。通过HackTimer库,可以有效避免因浏览器节能策略导致的服务端误判坐席离线的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

客服系统需要检测坐席是否下线,否则服务端将进线分配给离线的坐席,消费者得不到坐席的回应,影响消费者体验。系统为了检测消费者是否下线,增加了心跳逻辑,页面每2秒上报一次心跳。服务端30秒内未收到心跳,就认为坐席下线。
现网偶发坐席不断切换状态(上线->下线->上线->下线…)
经定位现代浏览器为了省电,当页面切到后台(非当前活动页),5分钟后浏览器会降低js定时器的执行频率(1分钟一次)。导致心跳机制受到影响。
该设计具体可以google(Throttling Javascript Timers to Reduce Battery Usage in Background Tabs)

解决方案

经google,截止到目前webworker不受该影响,可以通过webworker模拟settimeout、setInterval。
现在已有基于webworker,模拟settimeout、setInterval来规避该问题的开源库HackTimer
基本原理就是通过webworker模拟settimeout、setInterval并替换掉原生的settimeout、setInterval,源码在后面章节进行分析。先看测试效果。

准备工作

通过vue-cli创建demo

测试一

在HelloWorld.vue中添加setInterval,然后打开控制台并将页面切到后台,5分钟后观察日志打印。

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
setInterval(() => {
  console.log("log-"+new Date())
}, 25000);
</script>
结果

日志从8:52分开始打印,5分钟后日志打印变成1分钟一次。
在这里插入图片描述

测试二

安装HackTimer,并在入口(main.js)引入。

import 'hacktimer'
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
结果

日志从19:38分开始打印,5分钟后日志打印频率仍未下降,可见webworker目前仍然不受该限制。
在这里插入图片描述

hacktimer源码分析

webworker是为了解决js单线程性能受限引入的,其功能与线程相似,有些不与界面交互的逻辑放到webworker中运行,能够利用多核,提高页面性能。
hacktimer整理流程如下;简而言之,周期调度由WebWorker执行,WebWorker生成周期信号,驱动主线程执行handler:
在这里插入图片描述
hacktimer里面涉及几个技术点

  1. 通过window.URL.createObjectURL(blob)动态生成WebWorker的脚本
			var blob = new Blob (["\
var fakeIdToId = {};\
onmessage = function (event) {\
	var data = event.data,\
		name = data.name,\
		fakeId = data.fakeId,\
		time;\
	if(data.hasOwnProperty('time')) {\
		time = data.time;\
	}\
	switch (name) {\
		case 'setInterval':\
			fakeIdToId[fakeId] = setInterval(function () {\
				postMessage({fakeId: fakeId});\
			}, time);\
			break;\
		case 'clearInterval':\
			if (fakeIdToId.hasOwnProperty (fakeId)) {\
				clearInterval(fakeIdToId[fakeId]);\
				delete fakeIdToId[fakeId];\
			}\
			break;\
		case 'setTimeout':\
			fakeIdToId[fakeId] = setTimeout(function () {\
				postMessage({fakeId: fakeId});\
				if (fakeIdToId.hasOwnProperty (fakeId)) {\
					delete fakeIdToId[fakeId];\
				}\
			}, time);\
			break;\
		case 'clearTimeout':\
			if (fakeIdToId.hasOwnProperty (fakeId)) {\
				clearTimeout(fakeIdToId[fakeId]);\
				delete fakeIdToId[fakeId];\
			}\
			break;\
	}\
}\
"]);
			// Obtain a blob URL reference to our worker 'file'.
			workerScript = window.URL.createObjectURL(blob);
  1. 拦截(模拟)settimeout、setInterval
window.setInterval = function (callback, time /* , parameters */) {
				var fakeId = getFakeId ();
				fakeIdToCallback[fakeId] = {
					callback: callback,
					parameters: Array.prototype.slice.call(arguments, 2)
				};
				worker.postMessage ({
					name: 'setInterval',
					fakeId: fakeId,
					time: time
				});
				return fakeId;
			};
			window.clearInterval = function (fakeId) {
				if (fakeIdToCallback.hasOwnProperty(fakeId)) {
					delete fakeIdToCallback[fakeId];
					worker.postMessage ({
						name: 'clearInterval',
						fakeId: fakeId
					});
				}
			};
			window.setTimeout = function (callback, time /* , parameters */) {
				var fakeId = getFakeId ();
				fakeIdToCallback[fakeId] = {
					callback: callback,
					parameters: Array.prototype.slice.call(arguments, 2),
					isTimeout: true
				};
				worker.postMessage ({
					name: 'setTimeout',
					fakeId: fakeId,
					time: time
				});
				return fakeId;
			};
			window.clearTimeout = function (fakeId) {
				if (fakeIdToCallback.hasOwnProperty(fakeId)) {
					delete fakeIdToCallback[fakeId];
					worker.postMessage ({
						name: 'clearTimeout',
						fakeId: fakeId
					});
				}
			};
  1. setInterval、settimeout的callback注册到hacktimer自己私有的变量中,并监听WebWorker发送的调度信号,执行注册的方法。
			worker.onmessage = function (event) {
				var data = event.data,
					fakeId = data.fakeId,
					request,
					parameters,
					callback;
				if (fakeIdToCallback.hasOwnProperty(fakeId)) {
					request = fakeIdToCallback[fakeId];
					callback = request.callback;
					parameters = request.parameters;
					if (request.hasOwnProperty ('isTimeout') && request.isTimeout) {
						delete fakeIdToCallback[fakeId];
					}
				}
				if (typeof (callback) === 'function') {
					callback.apply (window, parameters);
				}
			};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值