页面中使用到定时器的注意啦📢这个坑可能你们也会有!

背景与问题

项目中,某些页面需要轮询请求更新页面,使用的是 setInterval。

但是我们发现,离开页面(组件)后,轮询并没有停止,而且是偶现,但我点击页面进入,然后很快切换出去的时候就会复现。第一直觉肯定是离开页面(组件)的时候,定时器没有清理。但排查代码之后发现,是有清理定时器的,类似代码如下:

js 复制代码
  mounted() {
    // 执行其他逻辑
    this.timer = setInterval(() => {
      console.log("我刷新了");
    }, 5000);
  },
  unmounted() {
    clearInterval(this.timer);
  },

那会是什么问题呢?仔细一看,在启动定时器之前还有一些其他逻辑,比如请求一个接口,需要等待返回才启动定时器。大概逻辑如下:

diff 复制代码
  async mounted() {
    // Perform other logic
+    // Execute a request
+   await new Promise((resolve) => {
+    setTimeout(() => resolve(), 1000)
+   });
+  console.log('First implementation');
    this.timer = setInterval(() => {
      console.log("I'm refreshing");
    }, 1000);
  },
  unmounted() {
    clearInterval(this.timer);
  },

示例代码可以看这里,示例代码1

所以这里的问题是,在还没启用定时器的时候,就已经离开这个页面(组件)了,而在我们离开页面(组件)之后,内存中的代码才开启定时器

为了更好的理解,可以看以下流程:

如何解决

方法一:立即启动定时器

既然是因为定时器启动晚导致的,那么进入页面(组件)就直接启动可能也是一种思路。但这种很明显缺点,不一定满足业务需求。因为定时器里面的逻辑很可能依赖前置一些耗时操作(比如请求得到的数据)。所以这个不建议使用。

方法二:通过标识离开页面(组件)

这个方法也很简单,就是定义一个辨识,比如 isLeavePage,初始化为 false,在离开页面(组件)的时候设置为 true。我们就可以在定时器逻辑中进行判断,如果为 true,则直接清除定时器。以下为简单示例代码:

diff 复制代码
  async mounted() {
    // Perform other logic
    // Execute a request
    await new Promise((resolve) => {
      setTimeout(() => resolve(), 1000);
    });
    console.log("First implementation");
    this.timer = setInterval(() => {
+      if (this.isLeavePage && this.timer) {
+      clearInterval(this.timer);
+       return;
+     }
      console.log("I'm refreshing");
    }, 1000);
  },
  unmounted() {
+    this.isLeavePage = true;
    clearInterval(this.timer);
  },

缺点是有一定的代码侵入性,但你可以看到,代码量还是比较少的。

方法三:不使用定时器

这种就是从根源上避开问题了,比如轮询的场景,可以使用类似 WebSockets / Server-Sent Events 等技术替代。这个这里不展开了。

可能的方法四:如何离开页面(组件)之后,不执行内存中代码执行?

上面的问题本质上还是离开页面(组件)之后,内存中代码依旧执行的问题,如果能做到销毁页面(组件)之后就不再执行内存中的代码,是不是就可以了呢?可惜目前我并没有看到 JS 可以做到这一点。

探索:轮询 setTimeout VS setInterval

这个章节实际上跟这个问题关系不大,如果不感兴趣,可以忽略!

大家看到,以上示例轮询,我使用的是 setInterval,实际上这可能会有一定问题的。

setTimeout 和 setInterval 将任务加入任务队列的区别:每个 setTimeout 产生的任务会直接 push 到任务队列中;而 setInterval 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)

这里假设,我们使用 setInterval 启动了一个间隔 100ms 的定时器,但实际上每次执行定时器中的逻辑时间超过 100ms。它的执行会如下所示:

  • 问题一:使用 setInterval 时,某些间隔会被跳过。从上图中可以看出,当 setInterval 要将第三个任务加入队列的时候(T3),T2 任务还没执行完,还处于队列中,这个时候,它就会直接跳过。但假如是 setTimeout 的话,它依旧会加入到队列中,不管之前是否有任务。

  • 问题二:可能多个定时器会连续执行。另外还有一个小点就是 T1 执行完之后,直接就执行了 T2,这种连续执行可能不一定符合我们的预期。

我们可以直接使用 setTimeout 模拟 setInterval:

js 复制代码
let timer = null
interval(func, wait){
    let interv = function(){
        func.call(null);
        timer=setTimeout(interv, wait);
    };
    timer= setTimeout(interv, wait);
},

如果还有疑问,推荐可以看看这篇文章(以上结论参考自此文):为什么要用 setTimeout 模拟 setInterval ?

总结

定时器,我们项目中经常会用到,而且看网上示例也都是在页面(组件)离开的时候就清除定时器即可。

但实际上,可能会因为前置操作逻辑比较耗时,导致用户离开页面(组件)之后,才开启定时器,这样可能会导致你的页面出现不及预期的表现。

本文分享了几种解决思路,希望对你有帮助,如果有其他的想法,也欢迎讨论!

相关推荐
Python私教12 分钟前
Go语言现代web开发15 Mutex 互斥锁
开发语言·前端·golang
A阳俊yi12 分钟前
Vue(13)——router-link
前端·javascript·vue.js
好看资源平台25 分钟前
前端框架对比与选择:如何在现代Web开发中做出最佳决策
前端·前端框架
4triumph28 分钟前
Vue.js教程笔记
前端·vue.js
程序员大金44 分钟前
基于SSM+Vue+MySQL的酒店管理系统
前端·vue.js·后端·mysql·spring·tomcat·mybatis
清灵xmf1 小时前
提前解锁 Vue 3.5 的新特性
前端·javascript·vue.js·vue3.5
Jiaberrr1 小时前
教你如何在微信小程序中轻松实现人脸识别功能
javascript·微信小程序·小程序·人脸识别·百度ai
程序员大金1 小时前
基于SpringBoot的旅游管理系统
java·vue.js·spring boot·后端·mysql·spring·旅游
白云~️1 小时前
监听html元素是否被删除,删除之后重新生成被删除的元素
前端·javascript·html
2401_864476931 小时前
无线领夹麦克风哪个降噪好?一文搞懂麦克风什么牌子的音质效果好
javascript·git·sql·github·mssql