前端交互体验优化-空闲时执行计算任务

我们知道,js 是单线程的脚本语言,单线程的特点就是所有代码只能按照线性先后顺序,同步的被执行。

即使我们说 js 可以执行异步任务,但本质是这些异步任务只是放在了等候队列,依然需要等待同步队列执行完成后,才能被加入到同步队列中,同步的去执行。

因此即使将计算任务放到异步队列中,它最终依然会被同步的执行,那么整个计算过程就会阻塞主线程的执行。

比如这样一个场景:点击"开始计算"按钮 执行1000*1000次的打印任务,紧接着点击"计时器"按钮

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>同步执行计算任务</title>
  <style>
    html, body {
      text-align: center;
      height: 100%;
    }
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
    }
  </style>
</head>

<body>

  <button id="clickMe">开始计算</button>
  <br />
  <button id="counter" style="margin-top: 20px;">Counter: 0</button>
  <script>
    const clickBtn = document.getElementById('clickMe');
    const counterBtn = document.getElementById('counter');

    let counter = 0;

    clickBtn.addEventListener('click', () => {
      calc();
    });

    counterBtn.addEventListener('click', () => {
      counter ++;
      console.log(`%cCounter Update: ${counter}`, 'color: green;');
      counterBtn.innerText = `Counter: ${counter}`
    });
    function calc() {
      for (let i = 0; i < 1000; i++) {
        console.log(`%cOuter Calc: ${i}`, 'color: gray;');
        for (let j = 0; j < 1000; j++) {
          console.debug(`${i}-${j}`);
        }
      }
    }
  </script>
</body>

</html>

在执行计算任务的过程中,会发现点击"计时器"按钮的事件长时间得不到响应,整个js线程都被阻塞在执行中的计算任务,也就是我们所说的卡死状态。直到任务计算完成,卡死状态才会结束,事件才会得到响应。

如果真实业务中存在这种大量的计算,且实现方式跟上面一样,可想而知,交互体验将会变的很糟糕!

假如我们的计算任务不能通过算法进行提效,那么应该怎么解决因大量计算导致的交互卡顿或卡死的问题呢?

下面有几种方案可供参考:

  1. 使用 webworker,开启子线程执行计算任务

  2. 使用requestIdleCallback 在主线程中,进行空闲时分片计算

  3. 如果业务可行,将计算任务交给其他端(如:BS架构的服务端)

webworker 虽然可以多线程并发,但是跨线程数据传输的性能损耗和瓶颈,对于数据量大的时候,带来的问题也会很明显。

我们这里主要分析,计算任务必须放在前端处理的场景,所以会采用方案:requestIdleCallback 空闲时分片计算进行举例。

依然是上面的例子,我们使用requestIdleCallback 空闲时接口进行改造

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>空闲时-分片执行计算任务</title>
  <style>
    html, body {
      text-align: center;
      height: 100%;
    }
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
    }
  </style>
</head>

<body>

  <button id="clickMe">开始计算</button>
  <br />
  <button id="counter" style="margin-top: 20px;">Counter: 0</button>
  <script>
    const clickBtn = document.getElementById('clickMe');
    const counterBtn = document.getElementById('counter');

    let counter = 0;

    clickBtn.addEventListener('click', () => {
      calc();
    });

    counterBtn.addEventListener('click', () => {
      counter ++;
      console.log(`%cCounter Update:${counter}`, 'color: green;');
      counterBtn.innerText = `Counter: ${counter}`
    });
    
    function calc() {
      let index = 0;
        
      // 将大的计算任务,拆分为小的计算任务,通过空闲时去分片执行
      function innerCalc(i) {
        console.log(`%cOuter Calc: ${i}`, 'color: gray;');
        for (let j = 0; j < 1000; j++) {
          console.debug(`${i}-${j}`);
        }
      }
      
      function outerCalc(deadline) {
        // 如果有空闲时间,或者计算等待超时,则执行计算任务
        while (index < 1000 && (deadline.timeRemaining() > 0 || deadline.timeout)) {
          innerCalc(index);
          index ++;
        }
        if (index < 1000) {
          requestIdleCallback(outerCalc, {timeout: 1000});
        } else {
          console.log("任务执行结束");
        }
      }
      requestIdleCallback(outerCalc, { timeout: 1000 });
    }
  </script>
</body>

</html>

可以看出来,当我们使用空闲时计算时,虽然计算任务一直在持续,但是并没有阻塞 "计时器" 的交互和更新。这会给交互体验,带来很大的改善。

使用空闲时执行计算任务,实现交互体验优化的关键有两个点:

  • 将计算任务的优先级降低,加入异步等待队列
  • 将大的计算任务拆分为小的任务,分片执行,当任务被执行时,不会长时间用户交互行为
相关推荐
foxhuli22933 分钟前
禁止ifrmare标签上的文件,实现自动下载功能,并且隐藏工具栏
前端
青皮桔1 小时前
CSS实现百分比水柱图
前端·css
失落的多巴胺1 小时前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear1 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息1 小时前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
青阳流月1 小时前
1.vue权衡的艺术
前端·vue.js·开源
样子20181 小时前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿1 小时前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘1 小时前
vue文本插值
javascript·vue.js·ecmascript
孤水寒月2 小时前
给自己网站增加一个免费的AI助手,纯HTML
前端·人工智能·html