Angular 中的可观察 Web Worker(8)- 简介

2025-06-10

Angular 中的可观察 Web Worker(8)- 简介

总结

Web Workers 非常棒,Angular CLI 现在原生支持它们。

可惜的是,Web Workers API 并不像 Angular,因此,我引入了一个库observable-webworker

如果您已经知道什么是 Web Worker 以及如何在 Angular 中使用它们,请随意跳过前面的内容,我将向您展示如何使用 将 CLI 生成的初始 Worker 重构为超级 Worker observable-webworker

语境

在本系列文章中,我们将探讨如何使用Web Workers将繁重的计算任务转移到另一个线程。保持主线程空闲以便与用户交互至关重要。在前端世界中,我们通常不会经常想到线程,因为 JavaScript 的非阻塞 API 和事件循环模型通常允许我们较长的运行进程(例如等待 HTTP 请求解析)之间执行用户交互事件。

因此,在大多数情况下,大多数任务不需要使用线程或 Web Worker。然而,有一些问题确实需要大量计算,这通常会阻塞主线程,从而影响用户交互(或 DOM 修改)。这些问题可能表现为动画卡顿、输入无响应、按钮无法立即响应等等。

解决这个问题的方案通常是在服务器端运行密集型计算,完成后将结果返回浏览器。然而,这种解决方案确实存在成本——你需要管理服务器 API,计算本身并非免费(你需要为服务器付费),而且如果你需要频繁地与计算交互,可能会出现严重的延迟问题。

幸运的是,Web Workers所有这些都有解决方案 - 它们允许您在浏览器中安排与主执行上下文并行运行的工作单元,然后一旦完成就可以将其结果传回主线程以在 DOM 中进行渲染等。

在 Angular 中创建你的第一个 Web Worker

工作者 API 相对简单 - 在 Typescript 中,你只需创建一个新的工作者

const myWorker = new Worker('./worker.js');

这一切都很好,但是我们喜欢使用 Typescript,并且能够导入其他文件等。局限于单个 javascript 文件不是很可扩展。

以前在 Angular 中使用 Worker(呵呵)相当麻烦,因为打包它们需要自定义 Webpack 配置。不过,Angular CLI 8 版本内置了对正确编译和打包 Web Worker 的支持。

文档位于https://angular.io/guide/web-worker但我们将在这里逐步介绍所有必需的内容(以及更多!)。

在开始之前,我们先建立一个空的 Angular CLI 项目

ng new observable-workers

接下来我们需要启用 Web Worker 支持,因此我们运行

ng generate web-worker

系统提示我们输入一个名称,因此我们将其命名为 demo。

Angular CLI 现在将更新tsconfig.app.jsonangular.json启用 Web Worker 支持,并为我们创建一个新的demo.worker.ts

/// <reference lib="webworker" />

addEventListener('message', ({ data }) => {
  const response = `worker response to ${data}`;
  postMessage(response);
});

为了让这个工作者运行起来,让我们更新我们的AppComponent

import { Component } from '@angular/core';

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

  runWorker() {

    const demoWorker = new Worker('./demo.worker', { type: 'module'});

    demoWorker.onmessage = (message) => {
      console.log(`Got message`, message.data);
    };

    demoWorker.postMessage('hello');

  }

}

模板如下:

<button (click)="runWorker()">Run Worker</button>

好的,现在一切准备就绪,如果还没运行,请运行应用程序,你会看到 DOM 中出现了一个按钮。是不是已经迫不及待了?

单击该吸盘,您将在开发控制台中看到

Got message worker response to hello

耶哈,我们从一个单独的线程中得到了回应,并且Angular 为我们编译了工作者

另外,为了证明这实际上是一个工作者,如果你转到SourcesChrome 中的选项卡,你会看到有一个工作者正在那里运行1.worker.js

我们仍然可以看到它在运行,这一点很重要——这意味着我们还没有销毁这个 Worker,尽管我们收到了它返回的唯一一条消息。如果我们再次点击按钮,我们将构造一个全新的 Worker,而旧的 Worker 将继续挂起。这可不是个好主意!我们应该在 Worker 用完后立即进行清理。

在我们担心如何销毁一个 worker 之前,让我们先回顾一下目前为止我们使用的 worker 的 API - 我们需要:

  • 构建工人
  • 声明一个属性并为其分配一个函数,以便从 worker 那里获取响应
  • addEventListener在工人内部,我们必须
  • 调用全局postMessage函数将信息发送回主线程

这个 API 感觉不太像 Angular,对吧?我们习惯了简洁的基于 hooks 的 API,也爱上了用 RxJS 来处理数据流和异步响应,为什么 Web Workers 不能也用上它呢?

嗯,我们可以。而这正是本文的重点。我想介绍一个新的库observable-webworker,它旨在解决这个笨重的 API,并给我们带来熟悉的 Angular 体验。

需要注意的是,这个库实际上完全不依赖于 Angular,它可以与 React 或任何其他框架完美兼容,甚至无需依赖任何框架!它唯一的依赖是peerDependencyRxJS。

为了最好地介绍库的概念,我们将重构我们当前的 Web 工作者以使用observable-webworker

实现 observable-webworker

首先,我们将安装observable-webworker。我正在使用 Yarn,但你知道如何安装软件包吧?!

 yarn add -E observable-webworker

首先,我们将更新AppComponent
<!-- embedme src/readme/observable-webworker-implementation/app.component.ts -->

import { Component } from '@angular/core';
import { fromWorker } from 'observable-webworker';
import { Observable, of } from 'rxjs';

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

  runWorker() {

    const input$: Observable<string> = of('hello');

    fromWorker<string, string>(() => new Worker('./demo.worker', { type: 'module'}), input$)
      .subscribe(message => {
        console.log(`Got message`, message);
      });

  }

}

注意,我们已经fromWorker从导入了observable-webworker。第一个参数是一个工作器工厂——我们需要能够按需延迟构建工作器,因此我们传递了一个工厂,库可以在需要时构建它。此外,Webpack 需要new Worker('./path/to/file.worker')在代码中找到 ,以便能够为我们打包它。
第二个参数input$是将发送给工作器的简单消息流。我们传递给工作器的泛型 ( <string, string>) 指示输入和输出类型。在我们的例子中,输入是一个非常简单的of('hello')

现在对于工人来说:

import { DoWork, ObservableWorker } from 'observable-webworker';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@ObservableWorker()
export class DemoWorker implements DoWork<string, string> {

  public work(input$: Observable<string>): Observable<string> {

    return input$.pipe(
      map(data => `worker response to ${data}`)
    );
  }

}

您可以看到,我们已经完全重构了工作者,以使用 Angular 开发人员更熟悉的 API - 我们使用一个装饰器,@ObservableWorker()它可以让库引导并发挥其魔力,并且我们实现了DoWork接口,这需要我们创建一个public work(input$: Observable<string>): Observable<string>;方法。

该方法的实际内容非常简单。我们map使用方法返回的简单方法处理可观察流。库将在后台调用该方法postMessage将数据返回到主线程。

现在,您可以再次运行该应用程序,它将像以前一样工作,但有一个例外,即由于不再需要进行工作,工作人员将自动终止。

由于我们现在使用可观察对象,因此库能够知道订阅者何时完成监听,并可以适当地清理工作者。

本系列的第一部分到此结束。在后续文章中,我们将讨论 Worker 更实际的实现,如何处理错误,以及如何在主线程和 Worker 线程之间传递非常大的对象。我可能还会深入研究 observable-webworker 的实现,因为它使用了一些比较奇特的 RxJS 操作符,例如materialize()dematerialize()

所有这些功能现在都可用observable-webworker,因此我鼓励您查看自述文件,因为它比我在本文中更详细地介绍了这些功能。


这是我的第一篇博文!如果您有任何反馈,无论好坏,我都乐意听取!

鏂囩珷鏉ユ簮锛�https://dev.to/zakhenry/observable-webworkers-with-angular-8-4k6
PREV
创建引人入胜的 GitHub 个人资料:分步指南 ɪ'ᴍ ᴋɪʀᴀɴ!
NEXT
可观察的 Web Workers,深入探讨实际用例