admin管理员组

文章数量:1530518

项目场景

使用setInterVal定时请求后台接口:

项目要求,每隔15s定时请求后台接口以记录进度,离开当前窗口请求不结束,除非手动关闭。


问题描述

setInterval次数丢失:

在循环定时中,偶尔会丢失一些次数,或者次数会连着一起请求,而不是指定的间隔时间。
查了原因是因为浏览器(这里只用了谷歌)一些节流机制


原因分析

当页面变为非活动状态时,WebKit 会自动采取措施来节省电量:

  • requestAnimationFrame停止。
  • CSS 和 SVG 动画被暂停。
  • 计时器受到限制。

页面变为非活动状态(不是用户的主要焦点)的情况有多种,例如:

  • 用户切换到不同的选项卡。
  • 用户切换到不同的应用程序。
  • 浏览器窗口最小化。
  • 浏览器窗口可见但不是焦点窗口。
  • 浏览器窗口位于另一个窗口的后面。
  • 窗口所在的空间不是当前空间。

解决方案

使用 Web Workers 发布setInterval任务:

使用 Web Workers。可以让浏览器窗口在非激活状态(或者最小化)也让setTimeout、setInterval有效不休眠的功能。

而且webWorkers还可以解决一个页面存在多个定时器时候间隔时间误差较大的问题

代码

新建js页面,例如:/@/utils/intervalWorker.js

// Build a worker from an anonymous function body
const blobURL = URL.createObjectURL(
  new Blob(
    [
      '(',

      function() {
        const intervalIds = {};

        // 监听message 开始执行定时器或者销毁
        self.onmessage = function onMsgFunc(e) {
          switch (e.data.command) {
            case 'interval:start': // 开启定时器
              const intervalId = setInterval(function() {
                postMessage({
                  message: 'interval:tick',
                  id: e.data.id,
                });
              }, e.data.interval);

              postMessage({
                message: 'interval:started',
                id: e.data.id,
              });

              intervalIds[e.data.id] = intervalId;
              break;
            case 'interval:clear': // 销毁
              clearInterval(intervalIds[e.data.id]);

              postMessage({
                message: 'interval:cleared',
                id: e.data.id,
              });

              delete intervalIds[e.data.id];
              break;
          }
        };
      }.toString(),

      ')()',
    ],
    { type: 'application/javascript' },
  ),
);

const worker = new Worker(blobURL);

URL.revokeObjectURL(blobURL);

const workerTimer = {
  id: 0,
  callbacks: {},
  setInterval: function(cb, interval, context) {
    this.id++;
    const id = this.id;
    this.callbacks[id] = { fn: cb, context: context };
    worker.postMessage({
      command: 'interval:start',
      interval: interval,
      id: id,
    });
    return id;
  },
  setTimeout: function(cb, timeout, context) {
    this.id++;
    const id = this.id;
    this.callbacks[id] = { fn: cb, context: context };
    worker.postMessage({ command: 'timeout:start', timeout: timeout, id: id });
    return id;
  },

  // 监听worker 里面的定时器发送的message 然后执行回调函数
  onMessage: function(e) {
    switch (e.data.message) {
      case 'interval:tick':
      case 'timeout:tick': {
        const callbackItem = this.callbacks[e.data.id];
        if (callbackItem && callbackItem.fn)
          callbackItem.fn.apply(callbackItem.context);
        break;
      }

      case 'interval:cleared':
      case 'timeout:cleared':
        delete this.callbacks[e.data.id];
        break;
    }
  },

  // 往worker里面发送销毁指令
  clearInterval: function(id) {
    worker.postMessage({ command: 'interval:clear', id: id });
  },
  clearTimeout: function(id) {
    worker.postMessage({ command: 'timeout:clear', id: id });
  },
};

worker.onmessage = workerTimer.onMessage.bind(workerTimer);

export default workerTimer;

调用,这里的引用填写自己上面创建 js 的路径

import workerTimer from '@/utils/intervalWorker'

// 开启
const currentInterval = workerTimer.setInterval(() => {
	// 执行方法
}, 30000);

// 注销
workerTimer.clearInterval(currentInterval);

文章来源:
参考:https://www.jianshu/p/beadd2a12df5
参考:https://www.jianshu/p/99535d3b7fd7

本文标签: 活跃浏览器状态页面setInterval