foreword
Web Workers
It 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 Workers
in Let me share some thoughts and specific implementation methods on worker
integration 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 API
Limitations: Same-origin restrictions, no DOM objects, asynchronous communication, so it is suitable for tasks that do not involve DOM operations 2.
Cost Worker
of 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 Amdahl
the formula , where F
is the required ratio for initialization and N
is the number of parallelism:
In conclusion, parallel computing-intensive tasks are suitable for Worker
use .
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 api
the 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 worker
practice .
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 worker
of call, with the main thread file above and worker
the 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. postMessage
The 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
worker
importScript
worker
worker
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 worker
class 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 promise
ization or rpc
ization
2. Whether functions can be dynamically created - can increase worker
flexibility
3. Whether it includes worker
multiple management capabilities, that is, thread pools
4. node
Consider 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 worker
multiple management; it can execute worker
functions defined in it a
; it can dynamically create a function and pass in parameters for worker
it 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 worker
the file to rewrite
2. Powerful enough: I can use public dependencies, such lodash
as some public functions already defined in the project
workerpool
Considering 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 worker
the 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:
worker
Define a public call function inside, inject the worker-depts dependency, and register it workerpool
in 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 worker
function as shown below.
Here we refer to a public function in a project fibonacci
, and also lodash
a map
method of , which can depts
be 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 workerDraw
wrapping ts
grammatically, 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
worker
After 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 environmentsworker
node
typeof 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