Introduction to Web Workers

We will explore how Web Workers providing useful examples for better understanding.

What are Web Workers?

Web Workers provides a way to run javascript scripts in a different context than the window context. They do not block the main execution thread.

         /----- Worker Thread
        /
-------O------- Main Execution Thread

To run javascript code in a Web Worker, we need to provide the Worker() constructor with the URL where the javascript resides. Once a Worker instance is created, it will run the script in a different context called DedicatedWorkerGlobalScope.

DedicatedWorkerGlobalScope is not reachable from the main thread. The only way Web Workers and the main execution thread can communicate between each other is by passing messages. For passing messages, they use the postMessage method.

Below we show how the main thread would communicate with the worker thread:

main.js

// The script must obey the same origin policy
// https://developer.mozilla.org/en/Same_origin_policy_for_JavaScript
const worker = new Worker(‘script-to-run-in-a-worker.js’)
worker.onmessage = function(evt) {
    console.log(evt.data)
}

// Pass a message to the worker
worker.postMessage('hello world')

The worker thread needs to prepare itself for listening messages. We can use onmessage or addEventListener('message') method to register a listener.

script-to-run-in-a-worker.js

self.onmessage = function(evt) {
    console.log(evt.data)
    self.postMessage('adios')
}

Since Web Workers runs in a different global context ( called DedicatedWorkerGlobalScope ) than the current window, they have access to a limited amount of APIs.

Why do we need Web Workers?

Web Workers make possible to run a script in a background thread, separated from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread without blocking the main execution thread.

What can I do with Web Workers?

In this section we will show some usages Web Workers examples. We need to remember that Web Workers are meant to be a long-lived, since they have a high start-up performance cost and a high per-instance memory cost.

Thus we should use them with moderation for intensive computational tasks, such as, compute prime numbers, obfuscating code in the browser or digital image processing.

Prime number finder using a web worker

In this example, we will use the a web worker to find prime numbers between 0 and the provided number.

main_thread.js

let myWorker = new Worker('web-worker.js')
myWorker.postMessage(100000)

web-worker.js


// Function: Evaluates if a number is prime.
// Params: Number
// Return: Boolean
const isPrime = (number) => {
    let prime = true
    for (let i = 2; i <= Math.sqrt(number); i++) {
        if (number % i == 0) {
            prime = false
            break
        }
    }
    return prime
}

// Function: Evaluates if a number is prime.
// Return: void
const findPrimeNumbersUntil = (maxNumber, postMessage) => {
    let number = 0
    while (number <= maxNumber) {
          number++
          if (isPrime(number)){
            // found a prime!
              postMessage(number);        
          }
    }
}

self.onmessage = function (message) {
    // Get number
    const number = message.data

    // Find prime numbers until given one
    findPrimeNumbersUntil(number, self.postMessage)
}

Obfuscating javascript files in a web worker

The following example will be an obfuscator tool. You upload your javascript package file and get the package obfuscated. Usually, this would involve sending the package to the backend where it would be processed. With Web Worker, the job can be done in a separate thread avoiding blocking the main thread.

Here you have your in-browser obfuscator:

const url = 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js'

let myWorker = new Worker('obfuscator_worker.js')

myWorker.onmessage = function(evt){
    const { obfuscated } = evt.data
    console.log('Vue obfuscated = ' + obfustated)
}

myWorker.postMessage({ url })

obfuscator_worker.js

importScript("https://cdn.jsdelivr.net/npm/javascript-obfuscator/dist/index.browser.js")

function obfuscated(text) {
    JavaScriptObfuscator.obfuscate(text,
    {
        compact: false,
        controlFlowFlattening: true
    }
  )
}

self.onmessage = function(evt){
    const { url } = evt.data
    fetch( url ).then( response => {
        response.text().then( text => {
          self.postMessage({obfuscated: ofuscated(text)})
        })
    })
}

Example: Image processing operation.

External articles you might be interested: