Recently, AFTER joining the new company, I took over the development of visual platform. After collecting opinions, it is found that users generally ridicule too much card when rendering. When you look at the interface, you see that the back end returns the raw results of the database query directly, requiring the page side to loop through various data in a silly way. When there is a lot of data, the user can just watch the browser freeze.
I believe we all have ideas about the optimization scheme. In the case that the back-end data body cannot be changed, the lowest cost is of course to introduce the Web Worker and throw the processing logic to it. However, due to various reasons, it is not feasible to build a Web Worker file and then ask for back-end dad deployment. So, imagine creating one dynamically at the page end?
Therefore, the following questions were raised:
- Whether it can be dynamically created
Web Worker
? - If it can be created, hopefully
API
The invocation is object-oriented and supportedPormise
The call. - This is created dynamically to save resources
Web Worker
Hopefully reusable.
With these questions in mind, go on a wild ride!
Dynamically createWeb Worker
What a Web Worker is, I believe we all know it well. The general reference method is to request a JS file, namely:
const worker = new Wokrer(worker's url);
Copy the code
So the question becomes, how do we take a function, turn it into a URL that the browser can recognize and reparse into that function? CreateObjectURL () {iframe and HTML5 blob implement javascript,CSS,HTML directly on the current page. “Use the url.createObjecturl () method to convert the Blob object to a URL object and give the SRC attribute to the iframe element we created.” So, can it be used to create urls for Web workers? Looking through the MDN documentation, I found this:
Note: This feature is available in Web Workers.
Xi Da pu Ben, plan to pass! The API accepts either a File object or a Blob object as a parameter. Here we use the Blob object as a parameter and specify the type as text/javascript.
function demo() { setTimeout(() => { postMessage('success! '); }, 1000) } const blob = new Blob([demo.toString() + ' demo()'], { type: 'text/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); worker.addEventListener('message', function(res) { console.log(res); })Copy the code
Open Chrome, type the code in the console, and after a second it will print: MessageEvent {isTrusted: true, data: “success!” , origin: “”, lastEventId: “”, source: null… }. Through the experiment, it can be confirmed that Web Worker can be created dynamically.
encapsulationAPI
Since you can create it dynamically, as a programmer, you don’t want to create it manually every time, so encapsulate it for easy use. Because it can be used in multiple places, it is packaged as a “class,” which helps with code reuse and memory savings. The constructor takes a function as an argument, and the “class” should have a send method to call when we want to send data again. Then the basic shelf should be:
Class DynamicWorker {constructor(cb) {constructor(cb) {// Create a Web Worker} send(data) {// Send data to the Web Worker.Copy the code
Since the created Web Worker does not process data immediately, but does so when the DynamicWorker instance calls the Send method, it should not write out postMessage as in the demo above. Instead, it should build an onMessage function. Thus the constructor function is constructed as follows:
constructor(cb) { const _fn = `const _fn = ${cb.toString()}; `; const _handle = ` onmessage = function ({ data }) { postMessage(_fn(data)); } `; const blob = new Blob([_fn + _handle], { type: 'text/javascript' }); this.worker = new Worker(URL.createObjectURL(blob)); RevokeObjectURL (blob); revokeObjectURL(blob); }Copy the code
A bit of explanation is why we need to create a _fn variable in the Web Worker. This is because when we generate Blob objects, we take an array of strings as arguments. If we just use cb.tostring (), we don’t get the function name. Therefore, it is impossible to execute this function in the Web Worker, so a variable is assigned and postMessage is used to return the result after the function is processed after the message event is triggered.
Then comes the send method, which returns a Promise and changes the state of the Promise after receiving the data from the Web Worker. According to this idea, there is a design like this:
send(data) { const worker = this.worker; let resolve = null; function _handleResult({ data }) { resolve(data); } worker.addEventListener('message', _handleResult); worker.postMessage(data); return new Promise((res) => { resolve = res; })}Copy the code
If you look at the code, it’s actually pretty straightforward. Copy the DynamicWorker code to the console and execute the following code:
const test = new DynamicWorker(function(data) {
return data;
})
test.send(123).then(res => console.log(res));
Copy the code
You’ll be happy to see your browser print 123.
Reuse and optimization
The above code works pretty much as expected, but there are a few problems. If you call send multiple times in a short period of time, the later method will produce the same result as the previous one. The worker.addEventListener(‘message’, _handleResult) does not distinguish between each call, so it will execute resolve after receiving the message. This results in invocation errors. This can be resolved by adding a flag bit. Another potential problem is adding too many event listeners to the worker. This is not necessary. One event listener for each DynamicWorker instance is sufficient. Combine these two points and modify the corresponding code, starting with constructor:
. const _handleResult = ({ data: { data, flag } }) => { const _res = this._map[flag]; if (_res) { _res(data); this._map[flag] = null; } } this._map = {}; this.worker.addEventListener('message', _handleResult); .Copy the code
Add these lines of code, and use _map to cache different tokens, which can distinguish different promises, based on the value of the flag bit. Similarly, the send method also needs to be modified to add flag generation. The complete DynamicWorker code is as follows:
class DynamicWorker { constructor(cb) { const _fn = `const _fn = ${cb.toString()}; `; const _handle = ` onmessage = function ({ data: { data, flag } }) { postMessage({ data: _fn(data), flag }); } `; const _handleResult = ({ data: { data, flag } }) => { const _res = this._map[flag]; if (_res) { _res(data); this._map[flag] = null; } } const blob = new Blob([_fn + _handle], { type: 'text/javascript' }); this.worker = new Worker(URL.createObjectURL(blob)); this._map = {}; this.worker.addEventListener('message', _handleResult); URL.revokeObjectURL(blob); } send(data) { const worker = this.worker; const flag = Math.random(); worker.postMessage({ data, flag, }); return new Promise((res) => { this._map[flag] = res; }}})Copy the code
It’s pretty straightforward, right? There is no change in calling the API, only the internal logic is optimized, interested students can copy into the console, write a function that takes a long time to throw in, try to see if the browser will not jam oh ~
summary
The DynamicWorker is still unfinished and lacks error handling, asynchronous processing and other features. Some of you may wonder what purpose such a thing is for, and there are some compatibility issues, as I did when I finished writing the code (because node.js will be connected next month and you can deploy your own files). After thinking about it, compared with normal use of Web Worker, the biggest advantage of dynamic creation is that it is dynamic, independent of server deployment files, and relatively flexible and easy to use. It is a feasible solution for some scenarios that rely on computing large amounts of data but cannot control the server.
Finally, there is a question that I would like to ask. Node.js is not a good choice for scenarios that need to process a large amount of data, but because of the poor performance of the page side and the relationship between the internal system, the processing logic is moved to Node.js, then what is the correct processing mode or architecture? Also hope to have the relevant experience of the students to give advice.
Thank you to see the adult here, easy to know, I hope this article is helpful to you ~ thank you!