This article is an extension of my previous article, “PWA Corner”, to introduce Web workers related to Service workers in PWA. I hope it can help you, and please give me more advice. More articles on the PWA topic will be published soon, so stay tuned.

Introduction to the

Web Worker (Worker thread) is a concept proposed in HTML5, divided into two types, Dedicated Web Worker and Shared Web Worker. A dedicated thread can only be used by the script that created it (one dedicated thread corresponds to one main thread), while a shared thread can be used in different scripts (one shared thread corresponds to multiple main threads).

Dedicated threads can be thought of as Web workers by default, with modifiers to distinguish them from shared threads. This paper will strictly distinguish the two, which may be cumbersome, but I personally think it is necessary. Where the word Web Worker appears purely refers to both.

use

The significance of Web Worker is that it can take some time-consuming data processing operations out of the main thread, making the main thread more focused on page rendering and interaction.

  • Lazy loading
  • The text analysis
  • Streaming media data processing
  • Canvas drawing
  • The image processing
  • .

Points to note

  • There is homology restriction
  • The DOM node cannot be accessed
  • Running in another context, the Window object cannot be used
  • The Web Worker runs without affecting the main thread, but is still constrained by the bottleneck of the single thread of the main thread when interacting with it. In other words, if the Worker thread interacts frequently with the main thread, the main thread may still block the page because it needs to handle the interaction
  • A shared thread can be called by multiple Browsing contexts, but all these must be the same source (same protocol, host and port number)

Browser support

According to CanI Use, about 93.05% of browsers currently support dedicated threads.

For shared threads, only about 41.66% of browsers support them.

Since the constructors for both dedicated and shared threads are included in the Window object, we can judge whether the browser supports them before using either.

if (window.Worker) {
    // ...
}
Copy the code
if (window.SharedWorker) {
    // ...
}
Copy the code

Thread creation

The dedicated thread is created by the Worker() method and can take two arguments. The first argument is the required location of the script, and the second argument is an optional configuration object that can specify the type, Credentials, and name properties.

var worker = new Worker('worker.js')
// var worker = new Worker('worker.js', { name: 'dedicatedWorker'})
Copy the code

Shared threads are created using the Shared Worker() method, which also supports two arguments and uses the same thing as the Worker() method.

var sharedWorker = new SharedWorker('shared-worker.js')
Copy the code

It is worth noting that because Web workers have the same origin restriction, they also need to be accessed by starting the local server when debugging locally. If opened directly using the file:// protocol, an exception will be thrown.

The data transfer

Both the Worker thread and the main thread send messages via the postMessage() method and receive messages via the onMessage event. In this process, data is not shared, but copied. Note that Error and Function objects cannot be copied by the structured cloning algorithm, and an attempt to do so will result in an exception being thrown DATA_CLONE_ERR. In addition, postMessage() can only send one object at a time. If you need to send more than one parameter, you can wrap the parameter as an array or object and pass it.

PostMessage () and The Structured Clone Algorithm will be discussed at The end of this article.

The following is an example of dedicated thread data transfer.

Var worker = new worker ();'worker.js')
worker.postMessage([10, 24])
worker.onmessage = function(e) {console.log(e.ata)} // Worker thread onmessage =function (e) {
    if (e.data.length > 1) {
        postMessage(e.data[1] - e.data[0])
    }
}
Copy the code

In Worker threads, both self and this represent global objects for the child thread. For listening to message events, the following four ways are equivalent.

Self.addeventlistener ();'message'.function(e) { // ... }) this.addeventListener ()'message'.function(e) { // ... }) addEventListener()'message'.function(e) { // ... }) // Write 4 onmessage =function (e) {
    // ...
}
Copy the code

The main thread uses MessagePort to access dedicated and shared threads. The port for a dedicated thread is automatically set when the thread is created and is not exposed. Unlike dedicated threads, shared threads must have the port open before they can deliver messages. The MDN MessagePort description of the start() method is:

Starts the sending of messages queued on the port (only needed when using EventTarget.addEventListener; it is implied when using MessagePort.onmessage.)

The start() method is used in conjunction with the addEventListener. If we select onMessage for event listening, then the start() method is implicitly called.

Var sharedWorker = new sharedWorker ();'shared-worker.js')
sharedWorker.port.onmessage = function(e) {// business logic}Copy the code
var sharedWorker = new SharedWorker('shared-worker.js')
sharedWorker.port.addEventListener('message'.function(e) {// business logic},false) sharedworker.port.start () // Needs to be explicitly openedCopy the code

When a message is delivered, the postMessage() method and the onMessage event must be invoked through the port object. In addition, in the Worker thread, the onConnect event needs to be used to listen for port changes, and the message processing function of the port needs to be used to respond.

/ / main thread sharedWorker. Port. PostMessage ([10, 24]) sharedWorker. Port. The onmessage =function(e) {console.log(e.ata)} // Worker thread onconnect =function (e) {
    let port = e.ports[0]

    port.onmessage = function (e) {
        if (e.data.length > 1) {
            port.postMessage(e.data[1] - e.data[0])
        }
    }
}
Copy the code

Close the Worker

The Worker can be closed using terminate() on the main thread or close() on the Worker thread. The two approaches are equivalent, but the preferred use is close() to prevent accidental closing of a running Worker thread. The Worker will not respond once the Worker thread is closed.

// Worker.terminate () // Dedicated worker thread self.close() // Shared worker thread self.port.close()Copy the code

Error handling

Errors can be handled by setting onError and onMessageError callback functions in the main thread or Worker thread. Among them, onError is executed when the error event of the Worker is triggered and bubbled, and onMessageError is triggered when the message received by the Worker cannot be deserialized (I have tried and failed to trigger onMessageError event. If an Error or Function object is passed in the worker thread using the postMessage method, it will be caught first by the onError method because it cannot be serialized and will not be deserialized at all.

// The main thread worker.onError =function() {/ /... } // The main thread uses a dedicated thread worker.onMessageError =function() {/ /... } / / main thread using Shared the worker thread. Port. Onmessageerror =function() {/ /... } // Worker thread onerror =function() {}Copy the code

Loading an external script

Web workers provide the importScripts() method to load external script files into the Worker.

importScripts('script1.js')
importScripts('script2.js'// This is equivalent to importScripts('script1.js'.'script2.js')
Copy the code

The child thread

A Worker can generate child workers, but there are two caveats.

  • The child Worker must be the same as the parent page
  • The URI in the child Worker is resolved relative to the location of the parent Worker

Embedded Worker

Currently, there is no label that can make Worker code embedded in web pages like

<script id="worker" type="javascript/worker"> // This code is not directly parsed by the JS engine, because the type is'javascript/worker'</script> <script> var workerScript = document.querySelector();'#worker').textContent
    var blob = new Blob(workerScript, {type: "text/javascript"})
    var worker = new Worker(window.URL.createObjectURL(blob))
</script>
Copy the code

About the postMessage

In Web Worker, The structured Clone algorithm is used for data communication between Worker threads and The main thread. Structured cloning algorithm is an algorithm that builds clones from input objects recursively, avoiding infinite traversal loops by saving mappings of previously accessed references. This process can be understood as using a method similar to json.stringfy () to serialize the parameters on the sender and a method similar to json.parse () to deserialize the parameters on the receiver.

However, a single transfer of data requires both serialization and deserialization, and this process itself can cause performance problems if the data volume is large. Therefore, the concept of Transferable Objects is proposed in Worker. When the amount of data is large, we can choose to directly transfer the data in the main thread to Worker thread. It is important to note that this transfer is complete and that once the data is successfully transferred, the main thread will not be able to access the data. The handover process is still passed through postMessage.

postMessage(message, transferList)
Copy the code

For example, pass an ArrayBuffer object

let aBuffer = new ArrayBuffer(1)
worker.postMessage({ data: aBuffer }, [aBuffer])
Copy the code

context

The Worker works in the context of a WorkerGlobalDataScope. Each WorkerGlobalDataScope object has a different event loop. The Event Loop has no browsing context associated with it, and its task queue consists of events, callbacks and networking activities.

Each WorkerGlobalDataScope has a closing flag. When this flag is set to true, the task queue will discard all subsequent tasks that attempt to join the task queue. The existing tasks in the queue will not be affected unless otherwise specified. Meanwhile, the timer will stop working and all pending background tasks will be deleted.

Functions and classes that can be used in the Worker

Because the working context of the Worker is different from ordinary browser context, it cannot access Windows and window-related apis, nor directly operate the DOM. Worker provides the WorkerNavigator and WorkerLocation interfaces, which are subsets of Navigator and Location in window, respectively. In addition, the Worker also provides many kinds of interfaces involving time, storage, network, drawing, etc., some of which are listed below. For more interfaces, please refer to the MDN document.

Time correlation

  • clearInterval()
  • clearTimeout()
  • setInterval()
  • setTimeout

The Worker related

  • importScripts()
  • close()
  • postMessage()

Store related

  • Cache
  • IndexedDB

Network related

  • Fetch
  • WebSocket
  • XMLHttpRequest

A link to the

reference

  • To use Web Workers – Web APIs | MDN
  • Worker | MDN
  • MessagePort | MDN
  • HTML Standard – Web workers
  • Basic information about Web Workers

Further reading

  • Canvas Best Practice – Taobao FED
  • Worklet