Recently, the boss said that it took a long time to calculate the function of message, resulting in the subsequent rendering directly stuck. He asked me to investigate whether web worker could be used to process the function of message processing to see if it could solve the blocking problem of rendering. Then I started to practice.

What is a Web Worker

The multi-threaded environment that modern browsers create for JavaScript. Some tasks can be created and assigned to worker threads for parallel operation. The two threads can run independently without interfering with each other and can communicate with each other through their own information mechanism.

Const worker = new worker ('work.js') const worker = new worker ('work.js') PostMessage ('message from main thread ') // Main thread passes information to worker worker. Terminate () // Main thread closes worker //worker Self.postmessage ('message from worker') // The worker passes information to the main thread. Self is a global object in the worker. Self.close () // The worker thread closes itselfCopy the code

Limitations:

  • The same-origin restrictions
  • Cannot use the document/window/alert/confirm
  • Unable to load local resource

Transformation of Web Worker

The interaction between Web workers is similar to the interaction with iframe, because postMessage is an asynchronous task. If used directly, there is no way to ensure the timing of the return of the message. Therefore, we can do some processing on the Web Worker to make it return a promise internally to make the timing of the return data controllable. Similar to below:

myWorker.postMessage('message').then(res => console.log(res))
Copy the code

Then I went to Git to find the library PROMISe-worker I wanted, and looked at its internal implementation. It was roughly divided into two files, one was created in the main file, the other was used in the worker file, because there were few design scenarios, so I made some modifications, and then it was my own

Create Worker in the main file

Let messageIds = 0 export class PromiseWorker {constructor(worker) {this.worker = worker this.callbacks = {} } postMessage(userMessage) { let messageId = messageIds++ let messageToSend = [messageId, Return new Promise((resolve,)) Reject) => {// create a new messageId each time the callback store is stored internally, This. Callbacks [messageId] = (error, error) Resolve (result) => {if (error) {reject(new error (error.message))} resolve(result) We found that windox.addeventListener ('message') could not be listened to, and since the worker also supports channels, we directly used the MessageChannel interface to allow us to create a new message channel. Const channel = new MessageChannel() // External use port1 to listen for the message the worker sends back channel.port1. onMessage = e => {onMessage(this, e)} // Worker.postMessage (aMessage, transferList) // The second argument, the transferList type, is an array of Transferable objects, The instance object used to pass ownership // MessagePort is the Transferable object so we can send port2 inside the worker to pass the message this.worker.postMessage(messageToSend, [channel.port2])})}} function onMessage(self, e) {let message = e.ata if (! Array.isArray(message) || message.length < 2) return let [messageId, error, Let callback = self.callbacks[messageId] if (! Callback) return self.callbacks[messageId] // Pass the error returned by the worker and result callback(error, result)}Copy the code

Worker files internal processing functions

// The worker's internal registration function is passed a callback function, Export function registerPromiseWorker(callback) {// where the message is sent Function postOutgoingMessage(e, messageId, error, result) {// If there is an error, send a message [messageId, error] if (error) { e.ports[0].postMessage([ messageId, { message: [messageId, null, result] e.posts [0].postMessage([messageId, null, result].postmessage ([messageId, null, result] e.posts [0].postMessage([messageId, null, result]) result]) } } function tryCatchFunc(callback, message) { try { return { res: callback(message) } } catch (e) { return { err: Function handleIncomingMessage(e, callback, messageId, Let result = tryCatchFunc(callback, message) if (result.err) {postOutgoingMessage(e, e); messageId, result.err) } else if (! IsPromise (result.res)) {// postOutgoingMessage(e, messageId, null, Result.res)} else {// Fulfillment rejection result.res. Then (finalResult => { postOutgoingMessage(e, messageId, null, finalResult) }, finalError => { postOutgoingMessage(e, messageId, OnIncomingMessage (e) {let payload = e.data if (! Array.isArray(payload) || payload.length ! == 2) Let [messageId, message] = payload // Determine whether the callback can be executed. If (typeof callback! == 'function') { postOutgoingMessage(e, messageId, new Error('Please pass a function init register().')) } else { handleIncomingMessage(e, callback, messageId, Self.addeventlistener ('message'); self.addeventListener ('message'); self.addeventListener ('message') onIncomingMessage) }Copy the code

Final use

Because the Worker cannot use local files, it is not possible for us to directly new Worker(‘./test.worker.js’), so we need to add a worker-loader to process the worker.js file in Webpack

/ / first install the worker - loader NPM I worker - loader - D / / in addition there is a point of pit worker - loader no longer support webpack3 so if after 2.0.0 webpack3 NPM I need to install 1 version [email protected] -d // After the installation, configure the module in webpack.config.js: {//... Rules: / / /... {test: /. Worker. Js $/, / / match all the XXX. Worker. Js loader: 'the worker - loader'} / /... / /... } // test.worker.js import { registerPromiseWorker } from '.. // webworker/index' registerPromiseWorker(m => {return '${m} pong'}) // index './test.worker.js' import {PromiseWorker} from '.. /webworker/index' const pw = new worker () const pw = new worker () Pw.postmessage ('ping').then(result => {console.log(' return content ', Const workerURL = new URL('./test.worker.js', './test.worker.js', './test.worker.js', '. import.meta.url) const worker = new Worker(workerURL) const pw = new PromiseWorker(worker) Pw.postmessage ('ping').then(result => {console.log(' return content ', result)})Copy the code