Web Worker处理大型耗时性任务的使用

1,516 阅读4分钟

Web Workers 是 Web 浏览器提供的一种可以在后台线程中运行脚本的技术,这样就可以避免阻塞用户界面(UI)。Web Workers 允许开发者执行长时间运行的任务,如复杂的计算、文件处理、网络请求等,而不影响用户的交互体验。

JavaScript 是一种单线程的语言,这意味着在任何给定的时间点,它只能执行一个任务。然而,这也意味着如果 JavaScript 需要执行一个耗时的任务,比如复杂的计算或者从服务器获取大量数据,那么这个任务会阻塞主线程。因此Web Workers 允许开发者在后台线程上运行 JavaScript 脚本,从而释放主线程来处理 UI 更新和其他交互任务。这有助于保持应用程序的流畅性和响应性。

image.png

由于Worker是可以调用其他的线程工作,我们便可以将大型的耗时性任务交给它来完成。

当我们使用JS线程执行类似的耗时任务时,对于页面的加载速度将会是灾难级的,我们前端的首要任务本来就是将页面加载速度尽可能提高,而数据的完整也是必要的,那么我们启用Worker将是十分必要的。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="startCalculation">Start Calculation</button>
  <p id="result"></p>

<script>
    const button = document.getElementById('startCalculation');
    const resultElement = document.getElementById('result');
    button.addEventListener('click', () => {
      let sum=0
      for(let i=0 ;i<100000000;i++){
        sum+=i
      }
      resultElement.textContent = 'Sum: ' + sum;
    })
</script>
</body>
</html>

将这个计算内容放入Worker线程,再去重新调用:

self.onmessage = function (event) {
  if (event.data.action === 'start') {
    // 开始执行耗时任务
    let sum = calculateSum();
    // 将结果发送回主线程
    self.postMessage(sum);
  }
};

function calculateSum() {
  let sum = 0;
  for (let i = 1; i <= 100000000; i++) {
    sum += i;
  }
  return sum;
}

这里我们解读一下这段代码:

   button.addEventListener('click', () => {
      // 创建一个新的 Worker
      const worker = new Worker('./worker.js');

      worker.onmessage = (event) => {
        // 接收来自 Worker 的消息
        setTimeout(() => {

        resultElement.innerHTML = 'Sum: ' + event.data;
        },1000);
      };

      // 向 Worker 发送开始信号
      worker.postMessage({ action: 'start' });
    });

Web Worker 通信流程

  1. 主线程向 Worker 发送消息

    • 主线程通过调用 worker.postMessage 方法向 Worker 发送数据。
  2. Worker 接收消息并执行任务

    • Worker 通过 self.onmessage 事件处理器接收来自主线程的消息。
    • 根据接收到的消息执行相应的任务。
  3. Worker 向主线程发送结果

    • 当任务完成时,Worker 使用 self.postMessage 方法向主线程发送结果。
    • 主线程通过 worker.onmessage 事件处理器接收这个结果。
  4. 主线程更新页面

    • 主线程接收到结果后,更新页面的内容以显示计算结果。

因此当我们去连续调用worker.postMessage去处理数据时,会不间断的一直触发worker.onmessage的监听反馈。

image.png

通过 worker.postMessage 方法向 Worker 发送了两个消息:一个是 { action: 'start' },另一个是 { action: 'print' }。当我向 Worker 发送消息时,这些消息被放入一个队列中。worker.postMessage 方法是异步的,这意味着当你发送消息时,主线程不会等待 Worker 处理完消息后再继续执行。

先从上面的打印可以直接的反馈出Worker的事项顺序:

  • Worker 首先处理 { action: 'start' } 消息,开始计算。
  • 一旦计算完成,Worker 会发送结果给主线程。
  • 此时 onmessage 事件处理器被调用,更新页面上的结果。
  • 接下来 Worker 处理 { action: 'print' } 消息,并可能执行其他操作(如打印结果到控制台)。

而示例中,onmessage 事件处理器内的逻辑之所以最后执行,是因为它取决于 Worker 处理完消息并将结果发送回主线程。一旦 Worker 完成计算并将结果发送给主线程,onmessage 事件处理器就会被调用,并执行更新页面内容和输出结果到控制台的逻辑。因此如果是耗时性不大的任务是可能会在 { action: 'start' }立即完成然后触发onmessage内的执行逻辑的。

除此之外:Web Worker 有以下几个使用注意点。

(1)同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM 限制

Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以navigator对象和location对象。

(3)通信联系

Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

(4)脚本限制

Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

(5)文件限制

Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

部分引用:Web Worker 使用教程 - 阮一峰的网络日志 (ruanyifeng.com)