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

我们知道,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>

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

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

  • 将计算任务的优先级降低,加入异步等待队列
  • 将大的计算任务拆分为小的任务,分片执行,当任务被执行时,不会长时间用户交互行为
相关推荐
莹雨潇潇7 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr15 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ2 小时前
html+css+js实现step进度条效果
javascript·css·html
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html
john_hjy3 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd3 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js