2023 Web Worker Project Practices

foreword

Web WorkersIt is an old technology that was proposed in 2009, but it is relatively seldom used in many projects. Some articles discuss how to write demos, but there are few engineering and project-level practices. This article will combine the programs Web Workersin Let me share some thoughts and specific implementation methods on workerintegration into demo involved has been placed on github and attached at the end of the article for reference.

Let me briefly introduce it first Web Workers, it is a technology that can run in the background thread of the Web application, independent of the main thread. As we all know, the JavaScript language is a single-threaded model, and by using it Web Workers, we can create a multi-threaded environment, so that we can take advantage of the multi-core CPU capabilities of modern computers, and have more benefits when dealing with increasingly large-scale Web programs.

The macro-semantics of Web Workers include three different types of Workers: DedicatedWorker(专有worker), SharedWorker(共享Worker), and ServiceWorker. This article discusses the first type, and you can study the other two types by yourself.

Introducing Web Workers

When introducing a new technology, usually we will consider the following questions: 1. What is the compatibility? 2. Where is the usage scenario?

Question 1, Web Workers was a proposal in 2009, and it was basically supported by major browsers in 2012. After 11 years, it is no problem to use it now.

Question 2 mainly considers the following three points:

1. Worker APILimitations: Same-origin restrictions, no DOM objects, asynchronous communication, so it is suitable for tasks that do not involve DOM operations 2.
Cost Workerof use: creation time + data transmission time; considering pre-creation, creation time can be ignored, only Considering the cost of data transmission, here you can refer to a test Is postMessage slow in 2019. The brief conclusion is relatively optimistic, and the speed is not the bottleneck in most equipment and data situations
. Multi-core capability, the closer the number of parallel tasks is to the number of CPUs, the higher the benefit. For the calculation of income in multi-threaded scenarios, you can refer to Amdahlthe formula , where Fis the required ratio for initialization and Nis the number of parallelism:

In conclusion, parallel computing-intensive tasks are suitable for Workeruse .

Worker practice

After the introduction worker, a question arises: Why has a technology with good compatibility and concurrency capabilities (sounds very tempting) not been used on a large scale yet?

I understand that there are two reasons: one is that there is no usage scenario with perfect matching, so the introduction has been put on hold; the other is that worker apithe design is too difficult to use, referring to many demos, it is troublesome to limit multiple configurations, which is prohibitive. This article will mainly focus on the second point, hoping to provide some mature engineering ideas for your workerpractice .

As for the first reason, in such a volume front-end field, when you already have a useful hammer in your hand, can't you find the nail that needs to be smashed?

How hard is Worker to use

Here's an example workerof call, with the main thread file above and workerthe file below:

// index.js
const worker = new Worker("./worker.js");
worker.onmessage = function (messageEvent) {console.log(messageEvent);
}; 
// worker.js
importScripts("constant.js");
function a() {console.log("test");
} 

Among the problems are:

1. postMessageThe way of passing messages is not suitable for modern programming models. When multiple events occur, it involves splitting, parsing and solving coupling problems, so it needs to be modified .
3. Filesworker can support defining functions, and can introduce dependent files through the method, but they are independent of the main thread file, and the reuse of dependencies and functions needs to be modified . Use and management need to be handled by yourselfworker
workerimportScript
workerworker

After reading so many questions, do you feel that your head is too big, how can you use such an original API comfortably?

Class library research

The first thing you can think of is to use the power of mature class libraries. The following table shows several common workerclass libraries . Among them, the key capabilities that we may pay attention to are:

1. Whether the communication is packaged in a more useful way, such as promiseization or rpcization
2. Whether functions can be dynamically created - can increase workerflexibility
3. Whether it includes workermultiple management capabilities, that is, thread pools
4. nodeConsider the usage scenarios, Is it possible to run cross-terminal

In comparison, workerpool wins. It is also a very old library. The earliest code was submitted 6 years ago, but there are no major problems in practice. The following will continue to discuss on the basis of using it.

Current status of workers supported by class libraries

By using it workerpool, we can create a new one in the main thread file worker; it automatically handles workermultiple management; it can execute workerfunctions defined in it a; it can dynamically create a function and pass in parameters for workerit execute.

// index.js
import workerpool from "workerpool";
const pool = workerpool.pool("./worker.js");
// 执行一个 worker 内定义好的函数
pool.exec("a", [1, 2]).then((res) => {console.log(res);
});
// 执行一个自定义函数
pool.exec((x, y) => {return x + y;}, // 自定义函数体[1, 2] // 自定义函数参数).then((res) => {console.log(res);}); 
// worker.js
importScripts("constant.js");
function a() {console.log("test");
} 

But this is not enough, in order to write code comfortably, we need further modification

Towards a comfortable and senseless worker writing

Our desired goals are:

1. Flexible enough: functions can be written at will. Today I want to calculate 1+1, and tomorrow I want to calculate 1+2. These can be written dynamically. It is best that it can be written directly in my own file in the main thread, and I don’t need to go to workerthe file to rewrite
2. Powerful enough: I can use public dependencies, such lodashas some public functions already defined in the project

workerpoolConsidering the ability to dynamically create functions, the first point can already be realized; while the second point is about dependency management, you need to build it yourself, and then introduce the building steps

1. Extract dependencies, manage compilation and updates:

Add a new dependency management file worker-depts.js, which can build an aggregate dependency object according to the path as the key name, and then introduce this dependency in workerthe file

// worker-depts.js
import * as _ from "lodash-es";
import * as math from "../math";

const workerDepts = {_,"util/math": math,
};

export default workerDepts; 
// worker.js
import workerDepts from "../util/worker/worker-depts"; 

2. Define the public call function, introduce the packaged dependencies and serialize the process:

workerDefine a public call function inside, inject the worker-depts dependency, and register it workerpoolin the method of

// worker.js
import workerDepts from "../util/worker/worker-depts";

function runWithDepts(fn: any, ...args: any) {var f = new Function("return (" + fn + ").apply(null, arguments);");return f.apply(f, [workerDepts].concat(args));
}

workerpool.worker({runWithDepts,
}); 

The corresponding call method is defined in the main thread file, and the input parameters are the custom function body and the parameter list of the function

// index.js
import workerpool from "workerpool";
export async function workerDraw(fn, ...args) {const pool = workerpool.pool("./worker.js");return pool.exec("runWithDepts", [String(fn)].concat(args));
} 

After completing the above steps, you can customize the workerfunction as shown below.

Here we refer to a public function in a project fibonacci, and also lodasha mapmethod of , which can deptsbe obtained on the object

// 项目内需使用worker时
const res = await workerDraw((depts, m, n) => {const { map } = depts["_"];const { fibonacci } = depts["util/math"];return map([m, n], (num) => fibonacci(num));},input1,input2
); 

3. Optimize syntax support

Dependency management without grammatical support is difficult to use. By workerDrawwrapping tsgrammatically, you can realize dependency prompts when using:

import workerpool from "workerpool";
import type TDepts from "./worker-depts";

export async function workerDraw<T extends any[], R>(fn: (depts: typeof TDepts, ...args: T) => Promise<R> | R,...args: T
) {const pool = workerpool.pool("./worker.js");return pool.exec("runWithDepts", [String(fn)].concat(args));
} 

Then you can get dependency hints when using:

4. Other issues

workerAfter adding , there are twowindow operating environments of and . If you happen to need to run on the compatible side like me, then there are three operating environments. Originally, we usually judge that the window environment uses this not enough now. Here you can Change to the globalThis object, which is an object that exists in all three environments, and the values ​​​​by judging are respectively , so as to realize the distinction between environmentsworkernodetypeof window === 'object'globalThis.constructor.name'Window' / 'DedicatedWorker'/ 'Object'

at last

I have compiled a set of "Interview Collection of Front-End Manufacturers", which includes HTML, CSS, JavaScript, HTTP, TCP protocol, browser, VUE, React, data structure and algorithm, a total of 201 interview questions, and made an answer for each question Answer and analyze.

Friends in need, you can click the card at the end of the article to receive this document and share it for free

Part of the documentation shows:



The length of the article is limited, and the following content will not be displayed one by one

Friends in need, you can click the card below to get it for free

Guess you like

Origin blog.csdn.net/Android_boom/article/details/129447413