前端websocket中定时器使用
更新于 阅读 32 次
websocket作为即时通信工具在前端广泛使用,消息发送之后如何判断超时,肯定会想到使用setTimeout
、setInterval
,我也是这么想的,所以就用到了代码了。既然文章开头都这么写,那大概率会出问题(^^)。
是的,真出问题了。心跳设置为10s
发送一次,但是当浏览器长时间切换到其他页面后,会发现后端断开了ws的链接,查看日志发现由于前端长时间未发送心跳引起的。
页面在被切到后台之后浏览器对非活跃标签进行了限制,所以定时器的延迟时间就不准了。后端长时间未收到心跳就断开链接。MDN上“制定有助于后台页面性能的策略”可以看到不活跃标签页中的setTimeout
和iframe
中的requestAnimationFrame
都会收到限制。
如何解决问题
既然主线程会收到限制,那么可以添加一个不受限制的线程。没错,说的就是web worker
。将定时器放在worker中,到时间后通知主线程即可。由于worker数据传输过程中会被序列化所以咱们不能直接把定时器的回调函数传输过去,只能在主线程中做一次映射。
下面咱们写一个简单的工具TimerUtil
,只提供setTimeout方法。演示地址。
页面开始显示"loading":
2s后显示"hello":
实现原理
- TimerUtil.setTimeout将业务的回调
timerHandle
存储在task
中,将延时timeout
发送到worker中,并将taskId返回给业务层,这里的taskId并不是真正的定时器id; - worker收到消息后判断类型,然后开始计时,并把
timerId
发送回TimerUtil,TimerUtil收到之后将timerId
存储到对应的task。 - worker中定时器结束后发送类型为
call
的消息,TImerUtil收到后,根据taskId
找到task
并执行timerHandle
,最后删除task任务. - 如果要手动clear任务,在TimerUtil中实现clearTimeout方法,根据taskId找到对应的task,获取对应的timerId,传递到worker中,worker调用系统的clearTimeout就可以清除任务了。
setIntveral
、clearIntveral
的实现类似。