Web Worker终极指南,我们将介绍web worker者并演示如何使用它们来解决执行速度问题。浏览器和服务器上的 JavaScript 程序在单个处理线程上运行。这意味着该程序一次只能做一件事。简单来说,您的新 PC 可能有一个 32 核 CPU,但当您的 JavaScript 应用程序运行时,其中 31 个处于闲置状态。
JavaScript 的单线程避免了复杂的并发情况。如果两个线程试图同时进行不兼容的更改,会发生什么情况?例如,一个浏览器可能正在更新 DOM,而另一个线程重定向到一个新的 URL 并从内存中擦除该文档。Node.js、Deno和 Bun 从浏览器继承了相同的单线程引擎。
这不是特定于 JavaScript 的限制。大多数语言都是单线程的,但 PHP 和 Python 等 Web 选项通常在 Web 服务器上运行,该服务器会针对每个用户请求在新线程上启动单独的解释器实例。这是资源密集型的,因此 Node.js 应用程序通常会定义自己的 Web 服务器,该服务器在单个线程上运行并异步处理每个传入请求。
Node.js 方法可以更有效地处理更高的流量负载,但长时间运行的 JavaScript 函数会抵消效率提升。
在我们演示如何解决 Web Worker 的执行速度问题之前,我们将首先检查 JavaScript 的运行方式以及为什么长时间运行的函数会出现问题。
JavaScript 非阻塞 I/O 事件循环
你可能认为一次做一件事会导致性能瓶颈,但 JavaScript 是异步的,这避免了大多数单线程处理问题,因为:
- 无需等待用户单击网页上的按钮。单击发生时,浏览器会引发调用 JavaScript 函数的事件。
- 无需等待对 Ajax 请求的响应。当服务器返回数据时,浏览器会引发调用 JavaScript 函数的事件。
- Node.js 应用程序无需等待数据库查询的结果。当数据可用时,运行时调用 JavaScript 函数。
JavaScript 引擎运行一个事件循环。一旦最后一条代码语句执行完毕,运行时将循环返回并在必要时执行回调之前检查未完成的计时器、挂起的回调和数据连接。
其他操作系统处理线程负责调用输入/输出系统,例如 HTTP 请求、文件处理程序和数据库连接。它们不会阻塞事件循环。它可以继续执行队列中等待的下一个 JavaScript 函数。
本质上,JavaScript 引擎只负责运行 JavaScript 代码。操作系统处理所有其他可能导致引擎发生的 I/O 操作,并在发生某些事情时调用 JavaScript 函数。
长时间运行的 JavaScript 函数
JavaScript 函数通常由事件触发。他们将进行一些处理,输出一些数据,并且大多数情况下会在几毫秒内完成,以便事件循环可以继续。
不幸的是,一些长时间运行的函数会阻塞事件循环。假设您正在开发自己的图像处理功能(例如锐化、模糊、灰度等)。异步代码可以从文件读取(或写入)数百万字节的像素数据——这对 JavaScript 引擎几乎没有影响。但是,处理图像的 JavaScript 代码可能需要几秒钟来计算每个像素。该函数会阻塞事件循环——在它完成之前,其他 JavaScript 代码都无法运行。
- 在浏览器中,用户将无法与页面进行交互。他们将无法单击、滚动或键入,并且可能会看到“无响应的脚本”错误以及停止处理的选项。
- Node.js 服务器应用程序的情况更糟。它不能在函数执行时响应其他请求。如果需要 10 秒才能完成,那么此时访问的每个用户都必须等待最多 10 秒——即使他们没有处理图像。
您可以通过将计算拆分为更小的子任务来解决问题。imageFn
以下代码使用传递的函数处理不超过 1,000 个像素(来自数组) 。setTimeout
然后它以1 毫秒的延迟调用自身。事件循环阻塞的时间较短,因此 JavaScript 引擎可以在迭代之间处理其他传入事件:
// pass callback function, image processing function, and input image // return an output image to a callback function function processImage( callback, imageFn = i => {}, imageIn = [] ) { const chunkSize = 1000; // pixels to process per iteration // process first chunk let imageOut = [], pointer = 0; processChunk(); // process a chunk of data function processChunk() { const pointerEnd = pointer + chunkSize; // pass chunk to image processing function imageOut = imageOut.concat( imageFn( imageIn.slice( pointer, pointerEnd ) ) ); if (pointerEnd < imageIn.length) { // process next chunk after a short delay pointer = pointerEnd; setTimeout(processChunk, 1); } else if (callback) { // complete - return output image to callback function callback( null, imageOut ); } } }
这可以防止无响应的脚本,但并不总是实用。单个执行线程仍然完成所有工作,即使 CPU 可能有能力做更多的事情。为了解决这个问题,我们可以使用web worker。
Web workers
Web workers允许脚本作为后台线程运行。worker 使用自己的引擎实例和独立于主执行线程的事件循环运行。它并行执行,不会阻塞主事件循环和其他任务。
要使用工作脚本:
- 主线程发布一条包含所有必要数据的消息。
- worker 中的事件处理程序执行并启动计算。
- 完成后,worker 会向主线程发送一条消息,其中包含返回的数据。
- 主线程中的事件处理程序执行、解析传入数据并采取必要的操作。
主线程——或任何 worker——可以产生任意数量的 worker。多个线程可以并行处理单独的数据块,从而比单个后台线程更快地确定结果。也就是说,每个新线程都有启动开销,因此确定最佳平衡可能需要进行一些试验。
所有浏览器、Node.js 10+、Deno 和 Bun 都支持具有相似语法的 worker,尽管服务器运行时可以提供更高级的选项。
专用与共享workers
浏览器提供两个工作选项:
- 专享 workers:由另一个脚本启动、使用和终止的单个脚本
- 共享 workers:不同窗口、iframe 或工作者中的多个脚本可以访问单个脚本
每个与共享工作者通信的脚本都会传递一个唯一的端口,共享工作者必须使用该端口来传回数据。但是,共享工作者在 IE 或大多数移动浏览器中不受支持,这使得它们在典型的 Web 项目中无法使用。
客户端worker 限制
一个 worker 与主线程和其他 worker 隔离运行;它不能访问其他线程中的数据,除非该数据显式传递给 worker。数据的副本被传递给worker。在内部,JavaScript 使用其结构化克隆算法将数据序列化为字符串。它可以包括原生类型,例如字符串、数字、布尔值、数组和对象,但不包括函数或 DOM 节点。
web worker可以使用控制台、Fetch、XMLHttpRequest、WebSocket 和 IndexDB 等 API。它们无法访问文档对象、DOM 节点、localStorage 和窗口对象的某些部分,因为这可能会导致 JavaScript 使用单线程解决的并发冲突问题——例如在重定向的同时更改 DOM。
重要提示:worker 最适合用于 CPU 密集型任务。它们不利于密集的 I/O 工作,因为它被卸载到浏览器并异步运行。
如何使用客户端web worker
下面的演示定义了主脚本,它在用户单击开始按钮src/index.js
时启动时钟并启动 web worker 。它定义了一个Worker 对象,其工作脚本的名称位于(相对于 HTML 文件):src/worker.js
// run on worker thread const worker = new Worker("./src/worker.js");
onmessage
接下来是事件处理程序。这在worker将数据发送回主脚本时运行——通常是在计算完成时。数据在事件对象的data
属性中可用,它传递给endDiceRun()
函数:
// receive data from worker worker.onmessage = function(e) { endDiceRun(e.data); };
主脚本启动 worker 使用它的postMessage()
方法来发送数据(一个名为 的对象cfg
):
// post data to worker worker.postMessage(cfg);
src/worker.js
定义工人代码。它src/dice.js
使用imports importScripts()
— 一种全局 worker 方法,该方法将一个或多个脚本同步导入到 worker 中。文件引用是相对于worker的位置:
importScripts('./dice.js');
src/dice.js
定义一个diceRun()
函数来计算投掷统计数据:
// record dice throwing statistics function diceRun(runs = 1, dice = 2, sides = 6) { const stat = []; while (runs > 0) { let sum = 0; for (let d = dice; d > 0; d--) { sum += Math.floor(Math.random() * sides) + 1; } stat[sum] = (stat[sum] || 0) + 1; runs--; } return stat; }
请注意,这不是ES 模块(见下文)。
src/worker.js
然后定义单个onmessage()
事件处理程序。这在主调用脚本 ( src/index.js
) 向 worker 发送数据时运行。事件对象具有.data
提供对消息数据的访问的属性。在本例中,它是cfg
具有属性.throws
、.dice
和的对象.sides
,它们作为参数传递给diceRun()
:
onmessage = function(e) { // start calculation const cfg = e.data; const stat = diceRun(cfg.throws, cfg.dice, cfg.sides); // return to main thread postMessage(stat); };
函数将postMessage()
结果发送回主脚本。这将调用worker.onmessage
上面显示的处理程序,它运行endDiceRun()
.
总之,线程处理是通过在主脚本和 worker 之间发送消息来实现的:
- 主脚本定义一个
Worker
对象并调用postMessage()
发送数据。 - 工作脚本执行
onmessage
启动计算的处理程序。 - worker调用
postMessage()
将数据发送回主脚本。 - 主脚本执行一个
onmessage
处理程序来接收结果。
Web Worker错误处理
除非您使用的是旧应用程序,否则现代浏览器中的开发人员工具支持web Worker 调试和控制台日志记录,就像任何标准脚本一样。
主脚本可以随时调用一个.terminate()
方法来结束worker。如果woker未能在特定时间内作出回应,这可能是必要的。例如,如果在 10 秒内没有收到响应,此代码将终止一个活跃的worker:
// main script const worker = new Worker('./src/worker.js'); // terminate worker after 10 seconds const workerTimer = setTimeout(() => worker.terminate(), 10000); // worker complete worker.onmessage = function(e) { // cancel termination clearTimeout(workerTimer); }; // start worker worker.postMessage({ somedata: 1 });
工作脚本可以使用标准的错误处理技术,例如验证传入数据、try
、catch
、finally
和throw
来优雅地处理出现的问题,并在需要时向主脚本报告。
您可以使用以下方法检测主脚本中未处理的worker错误:
onmessageerror
:当worker收到无法反序列化的数据时触发onerror
:当工作脚本中发生 JavaScript 错误时触发
返回的事件对象在 、 和 属性中提供错误.filename
详细.lineno
信息.message
:
// detect worker error worker.onerror = function(err) { console.log(`${ err.filename }, line ${ err.lineno }: ${ err.message }`); }
客户端Web Worker和 ES 模块
默认情况下,浏览器web worker无法使用 ES 模块(使用export
andimport
语法的模块)。
该src/dice.js
文件定义了一个导入到 worker 中的函数:
importScripts('./dice.js');
有点不寻常的是,该src/dice.js
代码也包含在主src/index.js
脚本中,因此它可以启动与工作进程和非工作进程相同的功能。src/index.js
作为 ES 模块加载。它不能加载代码,但它可以import
将src/dice.js
它作为 HTML<script>
元素加载,以便它在模块中可用:
const diceScript = document.createElement('script'); diceScript.src = './src/dice.js'; document.head.appendChild(diceScript);
这种情况在大多数应用程序中不太可能发生,除非您需要在主脚本和工作脚本之间共享代码库。
{ type: "module" }
通过向 worker 构造函数附加一个参数,可以在 worker 中支持 ES6 模块:
const worker = new Worker('./src/worker.js', { type: 'module' });
然后你可以export
在diceRun()
函数中src/dice.js
:
export function diceRun(runs = 1, dice = 2, sides = 6) { //... }
然后import
您在worker.js
模块中使用完全限定或相对 URL 引用:
import { diceRun } from './dice.js';
理论上,ES6 模块是一个不错的选择,但不幸的是,它们仅在 80 版(2020 年发布)的基于 Chromium 的浏览器中受支持。您不能在 Firefox 或 Safari 中使用它们,这使得它们对于此处显示的示例代码不切实际。
更好的选择是使用捆绑器,例如esbuild或rollup.js。这些可以解析 ES 模块引用并将它们打包到单个 worker(和 main)JavaScript 文件中。这简化了编码,并使worker明显更快,因为他们不需要在执行前解析导入。
客户端服务Service Worker
Service Worker是 Progressive Web Apps 用来提供离线功能、后台数据同步和 Web 通知的特殊 Web Worker。他们能:
- 作为浏览器和网络之间的代理来管理缓存文件
- 即使未加载浏览器或页面以更新数据和接收传入消息,也会在后台运行
与web worker一样,服务工作者在单独的处理线程上运行,不能使用 DOM 等 API。然而,这就是相似之处结束的地方:
- 主线程可以声明 service worker 的可用性,但两者之间没有任何直接通信。主线程不一定知道 service worker 正在运行。
- Service Worker 通常不用于 CPU 密集型计算。它们可以通过缓存文件和进行其他网络优化来间接提高性能。
- 一个特定的域/路径可以使用多个 web worker 来完成不同的任务,但它只能注册一个 service worker。
- Service Worker 必须在相同的 HTTPS 域和路径上,而 Web Worker 可以通过 HTTP 从任何域或路径进行操作。
服务器端 Web Worker 演示
Node.js 是最常用的服务器 JavaScript 运行时,它从版本 10 开始提供 worker。
Node.js 不是唯一的服务器运行时:
- Deno 复制了Web Worker API,因此语法与浏览器代码相同。它还提供了一种兼容模式,如果您想使用该运行时的工作线程语法,可以填充 Node.js API 。
- Bun处于测试阶段,尽管其目的是支持浏览器和 Node.js worker API。
- 您可能正在使用 JavaScript 无服务器服务,例如 AWS Lambda、Azure 函数、Google Cloud 函数、Cloudflare worker 或 Netlify edge 函数等。这些可能会提供类似 Web worker 的 API,尽管好处较少,因为每个用户请求都会启动一个单独的隔离实例。
以下演示显示了一个每秒将当前时间写入控制台的 Node.js 进程:在新的浏览器选项卡中打开 Node.js 演示。
然后在主线程上启动掷骰子计算。这会暂停当前正在输出的时间:
timer process 12:33:18 PM timer process 12:33:19 PM timer process 12:33:20 PM NO THREAD CALCULATION STARTED... ┌─────────┬──────────┐ │ (index) │ Values │ ├─────────┼──────────┤ │ 2 │ 2776134 │ │ 3 │ 5556674 │ │ 4 │ 8335819 │ │ 5 │ 11110893 │ │ 6 │ 13887045 │ │ 7 │ 16669114 │ │ 8 │ 13885068 │ │ 9 │ 11112704 │ │ 10 │ 8332503 │ │ 11 │ 5556106 │ │ 12 │ 2777940 │ └─────────┴──────────┘ processing time: 2961ms NO THREAD CALCULATION COMPLETE timer process 12:33:24 PM
完成后,相同的计算将在工作线程上启动。在这种情况下,时钟继续运行,同时进行骰子处理:
WORKER CALCULATION STARTED... timer process 12:33:27 PM timer process 12:33:28 PM timer process 12:33:29 PM ┌─────────┬──────────┐ │ (index) │ Values │ ├─────────┼──────────┤ │ 2 │ 2778246 │ │ 3 │ 5556129 │ │ 4 │ 8335780 │ │ 5 │ 11114930 │ │ 6 │ 13889458 │ │ 7 │ 16659456 │ │ 8 │ 13889139 │ │ 9 │ 11111219 │ │ 10 │ 8331738 │ │ 11 │ 5556788 │ │ 12 │ 2777117 │ └─────────┴──────────┘ processing time: 2643ms WORKER CALCULATION COMPLETE timer process 12:33:30 PM timer process 12:33:31 PM timer process 12:33:32 PM
工作进程通常比主线程快一点。
如何使用服务器端web worker
该演示定义src/index.js
了主脚本,它timer
在服务器收到新的 HTTP 请求时启动一个进程(如果它尚未运行):
// timer timer = setInterval(() => { console.log(` timer process ${ intlTime.format(new Date()) }`); }, 1000);
该runWorker()
函数定义了一个Worker 对象,其中工作脚本的名称位于src/worker.js
(相对于项目根目录)。workerData
它将变量作为单个值传递,在本例中,它是具有三个属性的对象:
const worker = new Worker("./src/worker.js", { workerData: { throws, dice, sides } });
与浏览器web worker不同,这会启动脚本。不需要 run worker.postMessage()
,尽管您可以使用它来运行parentPort.on("message")
worker 中定义的事件处理程序。
src/worker.js
代码调用这些值diceRun()
并将workerData
结果传递回主线程使用parentPort.postMessage()
:
// worker thread import { workerData, parentPort } from "node:worker_threads"; import { diceRun } from "./dice.js"; // start calculation const stat = diceRun(workerData.throws, workerData.dice, workerData.sides); // post message to parent script parentPort.postMessage(stat);
"message"
这会在主src/index.js
脚本中引发一个事件,该事件接收结果:
// result returned worker.on("message", result => { console.table(result); });
工人在发送消息后终止,这引发了一个"exit"
事件:
worker.on("exit", code => { //... clean up });
您可以根据需要定义其他错误和事件处理程序:
messageerror
:当worker收到无法反序列化的数据时触发online
: 当工作线程开始执行时触发error
:当工作脚本中发生 JavaScript 错误时触发。
内联工作脚本
单个脚本文件可以同时包含主代码和辅助代码。代码可以使用 来检查它是否在主线程上运行isMainThread
,然后调用自己作为工作者(import.meta.url
在 ES 模块或__filename
CommonJS 中用作文件引用):
import { Worker, isMainThread, workerData, parentPort } from "node:worker_threads"; if (isMainThread) { // main thread // create a worker from this script const worker = new Worker(import.meta.url, { workerData: { throws, dice, sides } }); worker.on("message", msg => {}); worker.on("exit", code => {}); } else { // worker thread const stat = diceRun(workerData.throws, workerData.dice, workerData.sides); parentPort.postMessage(stat); }
就个人而言,我更喜欢将文件分开,因为主线程和工作线程可能需要不同的模块。对于简单的单脚本项目,内联工作者可能是一种选择。
服务器端工作者限制
服务器worker仍然像在浏览器中一样独立运行并接收有限的数据副本。
Node.js、Deno 和 Bun 中的服务器端工作线程比浏览器工作线程具有更少的 API 限制,因为没有 DOM。当两个或多个 worker 试图同时将数据写入同一个文件时,您可能会遇到问题,但这在大多数应用程序中不太可能发生。
您将无法传递和共享复杂对象(例如数据库连接),因为大多数对象都具有无法克隆的方法和功能。但是,您可以执行以下操作之一:
- 在主线程中异步读取数据库数据并将结果数据传递给工作线程。
- 在 worker 中创建另一个连接对象。这将产生启动成本,但如果您的函数需要进一步的数据库查询作为计算的一部分,这可能是实用的。
重要提示:请记住,worker 最适合用于 CPU 密集型任务。它们不利于密集的 I/O 工作,因为它被卸载到操作系统并异步运行。
线程间共享数据
上面显示的主线程和工作线程之间的通信导致双方的数据被克隆。可以使用表示固定长度原始二进制数据的SharedArrayBuffer对象在线程之间共享数据。下面的主线程定义了从 0 到 99 的 100 个数字元素,并将其发送给一个 worker:
import { Worker } from "node:worker_threads"; const buffer = new SharedArrayBuffer(100 * Int32Array.BYTES_PER_ELEMENT), value = new Int32Array(buffer); value.forEach((v,i) => value[i] = i); const worker = new Worker("./worker.js"); worker.postMessage({ value });
worker可以接收value
对象:
import { parentPort } from 'node:worker_threads'; parentPort.on("message", value => { value[0] = 100; });
此时,主线程或工作线程都可以更改value
数组中的元素,并且它在两侧都发生了变化。
这种技术可以提高一些效率,因为没有必要在任一线程中序列化数据。有缺点:
- 您只能共享整数。
- 仍然需要发送消息以指示数据已更改。
- 存在两个线程可能同时更改相同值并失去同步的风险。
也就是说,该过程可能有利于需要处理大量图像或其他数据的高性能游戏。
Node.js Worker 的替代品
并非每个 Node.js 应用程序都需要或可以使用工作者。一个简单的 Web 服务器应用程序可能没有复杂的计算。它继续在单个处理线程上运行,并且随着活动用户数量的增加,响应速度会变慢。该设备可能具有更多的处理能力,具有多个未使用的 CPU 内核。
以下部分描述了通用的多线程选项。
Node.js 子进程
Node.js 在 worker 之前支持子进程,Deno 和 Bun 都有类似的功能。
本质上,他们可以启动另一个应用程序(不一定是 JavaScript)、传递数据并接收结果。他们以与工人类似的方式运作,但通常效率较低且流程更密集。
当您运行复杂的 JavaScript 函数时最好使用 Worker——可能在同一个项目中。当您启动另一个应用程序(例如 Linux 或 Python 命令)时,子进程变得必不可少。
Node.js 集群
Node.js 集群允许您分叉任意数量的相同进程以更有效地处理负载。最初的主进程可以自己分叉——可能为每个返回的 CPU 分叉一次os.cpus()
。它还可以在实例失败时处理重启,并在分叉进程之间代理通信消息。
标准库提供的cluster
属性和方法包括:
.isPrimary
true
:主要主要过程的回报(.isMaster
也支持旧的).fork()
: 生成一个子进程.isWorker
true
:工作进程的回报
此示例为设备上可用的每个 CPU/内核启动一个 Web 服务器工作进程。一台 4 核机器将生成四个 Web 服务器实例,因此它最多可以处理四倍的处理负载。它还会重新启动任何失败的进程,以使应用程序更加健壮:
// app.js import cluster from 'node:cluster'; import process from 'node:process'; import { cpus } from 'node:os'; import http from 'node:http'; const cpus = cpus().length; if (cluster.isPrimary) { console.log(`Started primary process: ${ process.pid }`); // fork workers for (let i = 0; i < cpus; i++) { cluster.fork(); } // worker failure event cluster.on('exit', (worker, code, signal) => { console.log(`worker ${ worker.process.pid } failed`); cluster.fork(); }); } else { // start HTTP server on worker http.createServer((req, res) => { res.writeHead(200); res.end('Hello!'); }).listen(8080); console.log(`Started worker process: ${ process.pid }`); }
所有进程共享端口 8080,并且任何进程都可以处理传入的 HTTP 请求。运行应用程序时的日志显示如下:
$ node app.js Started primary process: 1001 Started worker process: 1002 Started worker process: 1003 Started worker process: 1004 Started worker process: 1005 ...etc... worker 1002 failed Started worker process: 1006
很少有 Node.js 开发人员尝试集群。上面的示例很简单并且运行良好,但是当您尝试处理消息、故障和重新启动时,代码可能会变得越来越复杂。
流程经理
Node.js 进程管理器可以帮助运行 Node.js 应用程序的多个实例,而无需手动编写集群代码。最著名的是PM2。以下命令为每个 CPU/核心启动一个应用程序实例,并在它们失败时重新启动:
pm2 start ./app.js -i max
应用程序实例在后台启动,因此非常适合在实时服务器上使用。pm2 status
您可以通过输入(显示删节输出)来检查哪些进程正在运行:
$ pm2 status ┌────┬──────┬───────────┬─────────┬─────────┬──────┬────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ├────┼──────┼───────────┼─────────┼─────────┼──────┼────────┤ │ 1 │ app │ default │ 1.0.0 │ cluster │ 1001 │ 4D │ │ 2 │ app │ default │ 1.0.0 │ cluster │ 1002 │ 4D │ └────┴──────┴───────────┴─────────┴─────────┴──────┴────────┘
PM2 还可以运行用 Deno、Bun、Python 或任何其他语言编写的非 Node.js 应用程序。
容器管理器
集群和进程管理器将应用程序绑定到特定设备。如果您的服务器或操作系统依赖项发生故障,则无论运行实例的数量如何,您的应用程序都将失败。
容器是类似于虚拟机的概念,不同之处在于它们不是模拟完整的硬件设备,而是模拟操作系统。容器是围绕单个应用程序的轻量级包装器,具有所有必要的操作系统、库和可执行文件。它提供了 Node.js(或任何其他运行时)和您的应用程序的独立实例。单个设备可以运行多个容器,因此不需要集群或进程管理。
容器超出了本文的范围,但众所周知的解决方案包括Docker和Kubernetes。他们可以在任意数量的设备上启动和监控任意数量的容器,甚至在不同的位置,同时分配传入流量。
结论
JavaScript 工作者可以通过在并行线程中运行 CPU 密集型计算来提高客户端和服务器上的应用程序性能。服务器端工作者还可以通过在单独的线程中运行更危险的函数并在处理时间超过特定限制时终止它们来使应用程序更加健壮。
在 JavaScript 中使用 worker 很简单,但是:
- Worker 无法访问所有 API,例如浏览器 DOM。它们最适用于长时间运行的计算任务。
- 对于密集但异步的 I/O 任务(例如 HTTP 请求和数据库查询),Worker 的必要性较低。
- 启动 worker 会产生开销,因此可能需要进行一些实验以确保它们提高性能。
- 进程和容器管理等选项可能是比服务器端多线程更好的选择。
也就是说,当您遇到性能问题时,worker是一个有用的工具。
相关
JavaScript async/await初学者指南,附有示例