发布于 2026-01-06 4 阅读
0

如何在Angular中使用Web Worker在不同线程上执行函数

如何在Angular中使用Web Worker在不同线程上执行函数

作者:Vlado Tesanovic ✏️

借助Web Worker,我们可以在后台线程中执行脚本,从而让主线程可以专注于 UI 工作。默认情况下,Web Worker 接受文件 URL 作为参数,但在我们的场景中,这并不合适,因为我们主要使用 TypeScript 作为编程语言,并且不希望将其与 JavaScript 混用。第二个问题是脚本需要固定的 URL,而我们使用 Webpack 来打包和合并文件,因此使用未打包的文件并非最佳方案。

Worker类是ServiceWorkerSharedWorker的基础类。SharedWorker 与 Worker 类似,但它可以从多种不同的上下文中访问,包括弹出窗口、iframe 等。ServiceWorker 则完全不同,不在本次演示的讨论范围之内。

工作线程执行的代码与主线程执行的代码运行在不同的上下文中。在工作线程中运行代码时,我们无法操作 DOM 元素、使用 window 对象等等。工作线程运行的上下文称为DedicatedWorkerGlobalScope,其权限和可执行的操作都非常有限。

LogRocket 免费试用横幅

工作线程的常见用例包括执行繁重处理的纯函数。由于我们不希望它们降低 Web 应用程序的性能,因此应该将它们移到工作线程中。

工作线程可以通过消息传递postMessage方法与主线程通信。通信可以是双向的,这意味着工作线程和主线程可以互相发送消息。

主线程和工作线程

主线程和工作线程都可以互相监听和发送消息。

让我们创建一个InlineWorker类,它接受一个函数作为参数,并在另一个线程中运行该函数,如下所示:

import { Observable, Subject } from 'rxjs';

export class InlineWorker {

  private readonly worker: Worker;
  private onMessage = new Subject<MessageEvent>();
  private onError = new Subject<ErrorEvent>();

  constructor(func) {

    const WORKER_ENABLED = !!(Worker);

    if (WORKER_ENABLED) {
      const functionBody = func.toString().replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, '');

      this.worker = new Worker(URL.createObjectURL(
        new Blob([ functionBody ], { type: 'text/javascript' })
      ));

      this.worker.onmessage = (data) => {
        this.onMessage.next(data);
      };

      this.worker.onerror = (data) => {
        this.onError.next(data);
      };

    } else {
      throw new Error('WebWorker is not enabled');
    }
  }

  postMessage(data) {
    this.worker.postMessage(data);
  }

  onmessage(): Observable<MessageEvent> {
    return this.onMessage.asObservable();
  }

  onerror(): Observable<ErrorEvent> {
    return this.onError.asObservable();
  }

  terminate() {
    if (this.worker) {
      this.worker.terminate();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

上面代码中最重要的部分是一个类,它将一个函数转换为字符串,并创建ObjectURL一个对象,该对象将通过构造函数传递给工作类。

const functionBody = func.toString().replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, '');
this.worker = new Worker(URL.createObjectURL(
 new Blob([ functionBody ], { type: 'text/javascript' })
));
Enter fullscreen mode Exit fullscreen mode

如何使用 InlineWorker 类

假设我们在 Angular 中有一个函数(如上面代码块中显示的类),我们希望在后台处理它。

我们将开发一个应用程序,用于计算范围内有多少个质数。

主线程会将限制参数发送给工作线程,工作线程完成任务后会将结果返回给主线程并终止工作线程。

需要注意的是,我们不能使用在传递给 InlineWorker 的回调函数之外定义的任何方法、变量或函数。

如果需要传递参数(postMessage 函数接受任何参数),则必须使用postMessage方法。

import { Component, OnInit } from '@angular/core';
import { InlineWorker } from './inlineworker.class';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  result = 0;

  ngOnInit() {

    const worker = new InlineWorker(() => {
      // START OF WORKER THREAD CODE
      console.log('Start worker thread, wait for postMessage: ');

      const calculateCountOfPrimeNumbers = (limit) => {

        const isPrime = num => {
          for (let i = 2; i < num; i++) {
            if (num % i === 0) { return false; }
          }
          return num > 1;
        };

        let countPrimeNumbers = 0;

        while (limit >= 0) {
          if (isPrime(limit)) { countPrimeNumbers += 1; }
          limit--;
        }

        // this is from DedicatedWorkerGlobalScope ( because of that we have postMessage and onmessage methods )
        // and it can't see methods of this class
        // @ts-ignore
        this.postMessage({
          primeNumbers: countPrimeNumbers
        });
      };

      // @ts-ignore
      this.onmessage = (evt) => {
        console.log('Calculation started: ' + new Date());
        calculateCountOfPrimeNumbers(evt.data.limit);
      };
      // END OF WORKER THREAD CODE
    });

    worker.postMessage({ limit: 300000 });

    worker.onmessage().subscribe((data) => {
      console.log('Calculation done: ', new Date() + ' ' + data.data);
      this.result = data.data.primeNumbers;
      worker.terminate();
    });

    worker.onerror().subscribe((data) => {
      console.log(data);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

如我们所见,我们将一个匿名函数作为参数传递给了 InlineWorker。传递的函数的上下文是隔离的,这意味着我们无法访问其外部的任何内容。如果我们尝试这样做,它将是 undefined。

我们的应用程序流程大致如下:

应用程序流程

我们需要在 postMessage 和方法前面加上 @ts-ignore 注解onmessage,因为 TypeScript 无法读取当前上下文中的定义。在这种情况下,TypeScript 确实帮不上什么忙。

回调函数内部的监听器onmessage将监听传递给此工作线程的任何消息,在本例中,它将calculateCountOfPrimeNumbers使用传递的参数调用该工作线程。

函数会进行计算,并通过postMessage方法将结果传递给主线程上的监听器。

和:

worker.postMessage({ limit: 10000 });
Enter fullscreen mode Exit fullscreen mode

我们将触发一个工作线程的执行。由于本示例是用 Angular 编写的,我们将使用 RXJS 可观察对象来传递和监听数据变化。

下一行,我们将订阅来自工作进程的消息。

worker.onmessage().subscribe((data) => {
 console.log(data.data);
 worker.terminate();
});
Enter fullscreen mode Exit fullscreen mode

简单来说,我们将结果输出到控制台,然后终止工作线程,使其无法继续使用。我们可以向一个工作线程发送多条消息并接收多个结果,不像上面的示例那样会被锁定以执行单个任务。

订阅可观察对象非常重要,onerror因为这是查看工作线程中发生的错误的唯一方法。

演示时间

这里是使用 worker 实现的演示:https://angular-with-worker-logrocket.surge.sh/(不阻塞 UI)

演示工作实现

这是不使用 worker 的演示:https://angular-without-worker-logrocket.surge.sh/(计算运行时 UI 会被阻塞)

没有工人的演示

结论

在这篇文章中,我们学习了如何将繁重的处理任务从主线程转移到后台线程,而不会阻塞主线程,从而在我们的应用程序中提供良好的用户体验。

Web Workers 是Web API 的一部分,这意味着它们只能在浏览器中使用,值得注意的是,所有主流浏览器都对它们提供了良好的支持


编者按:发现本文有误?您可以在这里找到正确版本。

插件:LogRocket,一款用于 Web 应用的 DVR

 
LogRocket 控制面板免费试用横幅
 
LogRocket是一款前端日志工具,可让您重现问题,如同在您自己的浏览器中发生一样。无需猜测错误原因,也无需用户提供屏幕截图和日志转储,LogRocket 即可让您重现会话,快速了解问题所在。它与任何框架的应用程序完美兼容,并提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的额外上下文信息。
 
除了记录 Redux 操作和状态之外,LogRocket 还会记录控制台日志、JavaScript 错误、堆栈跟踪、包含标头和正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能生成像素级精确的视频。
 
免费试用


文章“如何在 Angular 中使用 web worker 在不同的线程上执行函数”最初发表于LogRocket 博客

文章来源:https://dev.to/bnevilleoneill/how-to-execute-a-function-with-a-web-worker-on-a- Different-thread-in-angular-akh