Adam Daniels

Adam Daniels

1562658363

Using Web Workers to Real-time Processing

In this article, we are going to illustrate how to use Web Workers. We will build a simple text analyzer and progressively enhance its implementation in order to avoid performance issues due to the JavaScript single-threaded processing model.

As a JavaScript developer, you should already know its single-threaded processing model: all of your JavaScript code is executed within a single thread. Even event handling and asynchronous callbacks are executed within the same thread and multiple events are processed sequentially, one after the other. In other words, there’s no parallelism in the execution of ordinary JavaScript code.

It may sound strange because that means JavaScript code is not fully exploiting your machine’s computing power. In addition, this model may cause some issues when a chunk of code takes too long to run. In this case, your application may become unresponsive.

Fortunately, recent web browsers provide a way to overcome this potential performance issue. The HTML5 specification introduces Web Workers to provide parallelism in JavaScript computing on the browser side.

Building a real-time text analyzer

Our goal is to implement a simple application showing some statistical data about a text as the user is typing it in a text area.

The HTML markup of the application looks something like this :

<textarea id="text" rows="10" cols="150" placeholder="Start writing...">
</textarea>

<div>
  <p>Word count: <span id="wordCount">0</span></p>
  <p>Character count: <span id="charCount">0</span></p>
  <p>Line count: <span id="lineCount">0</span></p>
  <p>Most repeated word: <span id="mostRepeatedWord"></span> (<span id="mostRepeatedWordCount">0</span> occurrences)</p>
</div>

You can see a textarea element, where the user can write their text, and a div element, where the application shows statistical data about the inserted text, such as word count, characters, lines, and the most repeated word. Remember that this data is shown in real time, while the user is writing.

The relevant JavaScript code extracting and displaying the statistical data is shown below:

const text = document.getElementById("text");
const wordCount = document.getElementById("wordCount");
const charCount = document.getElementById("charCount");
const lineCount = document.getElementById("lineCount");
const mostRepeatedWord = document.getElementById("mostRepeatedWord");
const mostRepeatedWordCount = document.getElementById("mostRepeatedWordCount");

text.addEventListener("keyup", ()=> {
  const currentText = text.value;
  
  wordCount.innerText = countWords(currentText);
  charCount.innerText = countChars(currentText);
  lineCount.innerText = countLines(currentText);
  let mostRepeatedWordInfo = findMostRepeatedWord(currentText);
  mostRepeatedWord.innerText = mostRepeatedWordInfo.mostRepeatedWord;
  mostRepeatedWordCount.innerText = mostRepeatedWordInfo.mostRepeatedWordCount;
});

Here you can see a block of statements getting the various DOM elements involved in displaying data and an event listener catching this data when the user finishes pressing each key.

Inside the body of the keyup event listener you find a few calls to the functions performing the actual data analysis: countWords(), countChars(), countLines() and findMostRepeatedWord(). You can find the implementation of these functions and the whole implementation of the text analyzer on CodePen.

Performance issues with the single thread

By analyzing the source code of this simple text analyzer application, you can see that the statistical extraction is performed each time the user finishes pressing a key on their keyboard. Of course, the computing effort related to the data extraction depends on the length of the text, so you might have a loss of performance while the text size grows.

Consider that the text analysis functions taken into account by this example are very easy, but you might want to extract more complex data such as keywords and their relevance, word classification, sentence length average and so on. Even if with a short or medium-length text this application might perform well, you could experience a loss of performance and getting the application to become unresponsive with a long text, especially when it is executed in a low-performance device, such as a smartphone.

Web Workers basics

The single-threaded processing model is intrinsic in the JavaScript language specification and it is applied both on the browser and on the server. To overcome this language restriction, the HTML5 specifications introduced the worker concept, that is an object providing a way to execute JavaScript code in a separate thread.

Creating a worker is straightforward: all you need is to isolate the code you want to execute in a separate thread in a file and create a worker object by invoking the *Worker()*constructor, as shown by the following example:

const myWorker = new Worker(“myWorkerCode.js”);

This type of worker is known as a Web Worker (another type of worker is the Service worker, but it is out of the scope of this article).

The interaction between the main thread and the worker’s thread is based on a message exchange system. Both the main thread and the worker’s thread can send messages by using the postMessage() method and receive messages by handling the message event.

For example, the main thread may start the worker’s thread by sending a message like this :

myWorker.postMessage(“start”);

As you can see, we passed the*start>*string as an argument for postMessage(), but you can pass whatever you want. It depends on you and on what your Web Worker expects but, remember, you cannot pass functions. Keep in mind, however, that data is passed by value. So, if you pass an object it will be cloned and any changes the worker makes on it will not affect the original object.

The worker receives the message by implementing a listener for the message event, as shown below:

self.addEventListener(“message”, (event) => {
  if (event.data === “start”) {
    //do things
  }
});

You can notice the self keyword. It refers to the current worker context, that is different from the global context of the main thread. You can also use the this keyword to refer to the worker context but, by convention, self is generally preferred.

So, in the example above, you attach the event listener to the current worker context and access the data coming from the main thread through the event.data property.

In the same way, the worker can send messages to the main thread by using postMessage():

self.postMessage(“ok”);

and the main thread receives messages by handling the message event, like this:

myWorker.addEventListener(“message”, (event) => {
  if (event.data === “ok”) {
    //do things
  }
});

Note that a worker may create another worker and communicate with it, so the interaction is not restricted to a worker and the main thread.

Finally, you can explicitly stop a worker in two ways: from inside the worker itself by invoking self.close() and from the calling thread by using the terminate() method, like in the following example:

myWorker.terminate();

A Web Worker for the text analyzer

After exploring the basics of Web Workers, let’s apply them to our application.

First, let’s extract the code to put in a separate file named textAnalyzer.js. You can take the opportunity to refactor the code by defining a function analyze() and returning the result of the text analysis, as shown here:

function analyze(str) {
  const mostRepeatedWordInfo = findMostRepeatedWord(str);
  
  return {
    wordCount: countWords(str),
    charCount: countChars(str),
    lineCount: countLines(str),
    mostRepeatedWord: mostRepeatedWordInfo.mostRepeatedWord,
    mostRepeatedWordCount: mostRepeatedWordInfo.mostRepeatedWordCount
  };
}

The other functions, countWords(), countChars() and so on, are defined in the same textAnalyzer.js file.

In the same file, we need to handle the message event in order to interact with the main thread. The following is the needed code:

self.addEventListener("message", (event) => {
  postMessage(analyze(event.data));
});

The event listener expects the text to be analyzed in the data property of the event object. Its only task is to simply return via postMessage() the result of applying the analyze() function to the text.

Now, the JavaScript code in the main script becomes as follows:

const text = document.getElementById("text");
const wordCount = document.getElementById("wordCount");
const charCount = document.getElementById("charCount");
const lineCount = document.getElementById("lineCount");
const mostRepeatedWord = document.getElementById("mostRepeatedWord");
const mostRepeatedWordCount = document.getElementById("mostRepeatedWordCount");

const textAnalyzer = new Worker("textAnalyzer.js");

text.addEventListener("keyup", ()=> {
  textAnalyzer.postMessage(text.value);  
});

textAnalyzer.addEventListener("message", (event) => {
  const textData = event.data;
  
  wordCount.innerText = textData.wordCount;
  charCount.innerText = textData.charCount;
  lineCount.innerText = textData.lineCount;
  mostRepeatedWord.innerText = textData.mostRepeatedWord;
  mostRepeatedWordCount.innerText = textData.mostRepeatedWordCount;
});

As you can see, we created the textAnalyzer Web Worker based on the textAnalyzer.js file.

Each time the user enters a key, a message is sent to the worker via postMessage() with the full text. The response from the worker comes from event.data in the form of an object, whose property values are assigned to the respective DOM elements for displaying.

Since the Web Worker’s code is executed in a separate thread, the user can continue inserting new text while the text analysis is in progress, without experiencing unresponsiveness.

Handling errors

What happens if an error occurs during the worker execution? In this case, an error event is fired and you should handle it in the calling thread through a normal event listener.

Suppose, for example, that our text analyzer worker checks if the data passed in the message is actually a text, like in the following code:

self.addEventListener("message", (event) => {
  if (typeof event.data === "string") {
    postMessage(analyze(event.data));    
  } else {
    throw new Error("Unable to analyze non-string data");
  }
});

The listener ensures that the passed data is a string before analyzing it and sending the message to the main thread. If the passed data is not a text, an exception is thrown.

On the main thread side, you should handle this exception by implementing a listener for the error event, as shown below:

textAnalyzer.addEventListener("error", (error) => {
  console.log(`Error "${error.message}" occurred in the file ${error.filename} at line ${error.lineno}`);
});

The event handler receives an error object with a few data about what went wrong. In the example we used:

  • the message property describes the error that occurred,
  • the filename property reports the name of the script file implementing the worker
  • the lineno property contains the line number where the error occurred

You can find the complete code of this implementation by following this link.

Web Workers restrictions

I hope you agree that Web Workers are amazing and very simple to use: you just need to use plain JavaScript and standard event handling for interoperation between the threads. Nothing particularly strange or complicated.

However, keep in mind that Web Workers have a few restrictions:

  • They cannot access the DOM either the window or the document objects. So, for example, don’t try to use console.log() to print messages on the browser’s console. This limitation along with passing serialized message data is necessary to make Web Workers thread-safe. It may seem too restrictive at first glance but, actually, this limitation guides you into a better separation of concerns and once you’ve learned how to deal with workers, the benefits will be clear.
  • In addition, Web Workers run only if the application’s files are served via HTTP or HTTPS protocol. In other words, they don’t run if your page is loaded from your local file system via file:// protocol.
  • Finally, the same origin policy also applies to Web Workers. This means that the script implementing the worker must be served from the same domain, including protocol and port, as the calling script.

Shared worker

As said before, Web Workers are used to implement expensive processing tasks in order to distribute the computational load. Sometimes the Web Worker may require a significant amount of resources, such as memory or local storage. When multiple pages or frames from the same application are opened, these resources are duplicated for each instance of the Web Worker. If the logic of your worker allows it, you could avoid growing resource requests by sharing the Web worker among multiple browser contexts.

Shared workers can help you. They are a variant of Web Workers we’ve seen so far. In order to distinguish this variant type from the previous ones, the latter are often called Dedicated workers.

Let’s take a look at how you can create a Shared worker by transforming our text analyzer.

The first step is to use the SharedWorker() constructor instead of Worker():

const textAnalyzer = new SharedWorker("textAnalyzer.js");

This constructor creates a proxy for the worker. Since the worker will communicate with multiple callers, the proxy will have a dedicated port that must be used to attach listeners and to send messages. So, you need to attach the listener for the message event as follows:

textAnalyzer.port.addEventListener("message", (event) => {
  const textData = event.data;
  
  wordCount.innerText = textData.wordCount;
  charCount.innerText = textData.charCount;
  lineCount.innerText = textData.lineCount;
  mostRepeatedWord.innerText = textData.mostRepeatedWord;
  mostRepeatedWordCount.innerText = textData.mostRepeatedWordCount;
});

Notice that the only difference is the use of the port property for attaching the event listener. In the same way, you need to use the port property to send a message via postMessage():

text.addEventListener("keyup", ()=> {
  textAnalyzer.port.postMessage(text.value);
});

Unlike before, however, you need to explicitly connect your thread to the worker thread by calling the start() method, as shown below:

textAnalyzer.port.start();

This is required to make sure that ports don’t dispatch events until the listener has been added. Keep in mind, however, that you don’t need to invoke start() if you attach your listener to the onmessage property instead of using addEventListener(), like this:

textAnalyzer.port.onmessage = (event) => {
  const textData = event.data;
  
  wordCount.innerText = textData.wordCount;
  charCount.innerText = textData.charCount;
  lineCount.innerText = textData.lineCount;
  mostRepeatedWord.innerText = textData.mostRepeatedWord;
  mostRepeatedWordCount.innerText = textData.mostRepeatedWordCount;
};

On the worker side, you need to arrange a bit the worker set up by replacing the message event listener with the following code:

self.addEventListener("connect", (event) => {
  const port = event.ports[0];

  port.addEventListener("message", (event) => {
    if (typeof event.data === "string") {
      port.postMessage(analyze(event.data));    
    } else {
      throw new Error("Unable to analyze non-string data");
    }
  });

  port.start();
});

You added a listener for the connect event. This event fires when a caller invokes the start() method of the worker proxy’s port or when it attaches an event listener to the onmessage property. In both cases, a port is assigned to the worker and you can get it by accessing the first element of the ports array of the event object. Similar to the caller, you need to use this port to attach event listeners and send messages. In addition, if you used addEventListener() to attach your listener, you need to establish a connection with the caller through the port.start() method.

Now your worker has become a shared worker.

The full code for this implementation is available at this link.

Conclusion

In this article, we discussed the limitations that the JavaScript single-threaded processing model may have in some scenarios. The implementation of a simple real-time text analyzer tried to better explain the issue.

Web Workers were introduced to solve the potential performance issues. They were used to spawn in a separate thread. We discussed the Web Workers restrictions and finally explained how to create shared workers when we need to share a Web Worker among multiple pages or frames.

You can find the final code of the workers created in this article in this GitHub repository.

#javascript #web-development #web-service #html #html5

What is GEEK

Buddha Community

Using Web Workers to Real-time Processing
Ian  Robinson

Ian Robinson

1621644000

4 Real-Time Data Analytics Predictions for 2021

Data management, analytics, data science, and real-time systems will converge this year enabling new automated and self-learning solutions for real-time business operations.

The global pandemic of 2020 has upended social behaviors and business operations. Working from home is the new normal for many, and technology has accelerated and opened new lines of business. Retail and travel have been hit hard, and tech-savvy companies are reinventing e-commerce and in-store channels to survive and thrive. In biotech, pharma, and healthcare, analytics command centers have become the center of operations, much like network operation centers in transport and logistics during pre-COVID times.

While data management and analytics have been critical to strategy and growth over the last decade, COVID-19 has propelled these functions into the center of business operations. Data science and analytics have become a focal point for business leaders to make critical decisions like how to adapt business in this new order of supply and demand and forecast what lies ahead.

In the next year, I anticipate a convergence of data, analytics, integration, and DevOps to create an environment for rapid development of AI-infused applications to address business challenges and opportunities. We will see a proliferation of API-led microservices developer environments for real-time data integration, and the emergence of data hubs as a bridge between at-rest and in-motion data assets, and event-enabled analytics with deeper collaboration between data scientists, DevOps, and ModelOps developers. From this, an ML engineer persona will emerge.

#analytics #artificial intelligence technologies #big data #big data analysis tools #from our experts #machine learning #real-time decisions #real-time analytics #real-time data #real-time data analytics

Siphiwe  Nair

Siphiwe Nair

1622608260

Making Sense of Unbounded Data & Real-Time Processing Systems

Unbounded data refers to continuous, never-ending data streams with no beginning or end. They are made available over time. Anyone who wishes to act upon them can do without downloading them first.

As Martin Kleppmann stated in his famous book, unbounded data will never “complete” in any meaningful way.

“In reality, a lot of data is unbounded because it arrives gradually over time: your users produced data yesterday and today, and they will continue to produce more data tomorrow. Unless you go out of business, this process never ends, and so the dataset is never “complete” in any meaningful way.”

— Martin Kleppmann, Designing Data-Intensive Applications

Processing unbounded data requires an entirely different approach than its counterpart, batch processing. This article summarises the value of unbounded data and how you can build systems to harness the power of real-time data.

#stream-processing #software-architecture #event-driven-architecture #data-processing #data-analysis #big-data-processing #real-time-processing #data-storage

Jessica Smith

Jessica Smith

1612606870

REAL TIME CHAT SOLUTIONS SERVICES FOR MOBILE APPS

Build a Real Time chat application that can integrated into your social handles. Add more life to your website or support portal with a real time chat solutions for mobile apps that shows online presence indicators, typing status, timestamp, multimedia sharing and much more. Users can also log into the live chat app using their social media logins sparing them from the need to remember usernames and passwords. For more information call us at +18444455767 or email us at hello@sisgain.com or Visit: https://sisgain.com/instant-real-time-chat-solutions-mobile-apps

#real time chat solutions for mobile apps #real time chat app development solutions #live chat software for mobile #live chat software solutions #real time chat app development #real time chat applications in java script

Ashish parmar

Ashish parmar

1627043546

Evolution in Web Design: A Case Study of 25 Years - Prismetric

The term web design simply encompasses a design process related to the front-end design of website that includes writing mark-up. Creative web design has a considerable impact on your perceived business credibility and quality. It taps onto the broader scopes of web development services.

Web designing is identified as a critical factor for the success of websites and eCommerce. The internet has completely changed the way businesses and brands operate. Web design and web development go hand-in-hand and the need for a professional web design and development company, offering a blend of creative designs and user-centric elements at an affordable rate, is growing at a significant rate.

In this blog, we have focused on the different areas of designing a website that covers all the trends, tools, and techniques coming up with time.

Web design
In 2020 itself, the number of smartphone users across the globe stands at 6.95 billion, with experts suggesting a high rise of 17.75 billion by 2024. On the other hand, the percentage of Gen Z web and internet users worldwide is up to 98%. This is not just a huge market but a ginormous one to boost your business and grow your presence online.

Web Design History
At a huge particle physics laboratory, CERN in Switzerland, the son of computer scientist Barner Lee published the first-ever website on August 6, 1991. He is not only the first web designer but also the creator of HTML (HyperText Markup Language). The worldwide web persisted and after two years, the world’s first search engine was born. This was just the beginning.

Evolution of Web Design over the years
With the release of the Internet web browser and Windows 95 in 1995, most trading companies at that time saw innumerable possibilities of instant worldwide information and public sharing of websites to increase their sales. This led to the prospect of eCommerce and worldwide group communications.

The next few years saw a soaring launch of the now-so-famous websites such as Yahoo, Amazon, eBay, Google, and substantially more. In 2004, by the time Facebook was launched, there were more than 50 million websites online.

Then came the era of Google, the ruler of all search engines introducing us to search engine optimization (SEO) and businesses sought their ways to improve their ranks. The world turned more towards mobile web experiences and responsive mobile-friendly web designs became requisite.

Let’s take a deep look at the evolution of illustrious brands to have a profound understanding of web design.

Here is a retrospection of a few widely acclaimed brands over the years.

Netflix
From a simple idea of renting DVDs online to a multi-billion-dollar business, saying that Netflix has come a long way is an understatement. A company that has sent shockwaves across Hollywood in the form of content delivery. Abundantly, Netflix (NFLX) is responsible for the rise in streaming services across 190 countries and meaningful changes in the entertainment industry.

1997-2000

The idea of Netflix was born when Reed Hastings and Marc Randolph decided to rent DVDs by mail. With 925 titles and a pay-per-rental model, Netflix.com debuts the first DVD rental and sales site with all novel features. It offered unlimited rentals without due dates or monthly rental limitations with a personalized movie recommendation system.

Netflix 1997-2000

2001-2005

Announcing its initial public offering (IPO) under the NASDAQ ticker NFLX, Netflix reached over 1 million subscribers in the United States by introducing a profile feature in their influential website design along with a free trial allowing members to create lists and rate their favorite movies. The user experience was quite engaging with the categorization of content, recommendations based on history, search engine, and a queue of movies to watch.

Netflix 2001-2005 -2003

2006-2010

They then unleashed streaming and partnering with electronic brands such as blu-ray, Xbox, and set-top boxes so that users can watch series and films straight away. Later in 2010, they also launched their sophisticated website on mobile devices with its iconic red and black themed background.

Netflix 2006-2010 -2007

2011-2015

In 2013, an eye-tracking test revealed that the users didn’t focus on the details of the movie or show in the existing interface and were perplexed with the flow of information. Hence, the professional web designers simply shifted the text from the right side to the top of the screen. With Daredevil, an audio description feature was also launched for the visually impaired ones.

Netflix 2011-2015

2016-2020

These years, Netflix came with a plethora of new features for their modern website design such as AutoPay, snippets of trailers, recommendations categorized by genre, percentage based on user experience, upcoming shows, top 10 lists, etc. These web application features yielded better results in visual hierarchy and flow of information across the website.

Netflix 2016-2020

2021

With a sleek logo in their iconic red N, timeless black background with a ‘Watch anywhere, Cancel anytime’ the color, the combination, the statement, and the leading ott platform for top video streaming service Netflix has overgrown into a revolutionary lifestyle of Netflix and Chill.

Netflix 2021

Contunue to read: Evolution in Web Design: A Case Study of 25 Years

#web #web-design #web-design-development #web-design-case-study #web-design-history #web-development

Adam Daniels

Adam Daniels

1562658363

Using Web Workers to Real-time Processing

In this article, we are going to illustrate how to use Web Workers. We will build a simple text analyzer and progressively enhance its implementation in order to avoid performance issues due to the JavaScript single-threaded processing model.

As a JavaScript developer, you should already know its single-threaded processing model: all of your JavaScript code is executed within a single thread. Even event handling and asynchronous callbacks are executed within the same thread and multiple events are processed sequentially, one after the other. In other words, there’s no parallelism in the execution of ordinary JavaScript code.

It may sound strange because that means JavaScript code is not fully exploiting your machine’s computing power. In addition, this model may cause some issues when a chunk of code takes too long to run. In this case, your application may become unresponsive.

Fortunately, recent web browsers provide a way to overcome this potential performance issue. The HTML5 specification introduces Web Workers to provide parallelism in JavaScript computing on the browser side.

Building a real-time text analyzer

Our goal is to implement a simple application showing some statistical data about a text as the user is typing it in a text area.

The HTML markup of the application looks something like this :

<textarea id="text" rows="10" cols="150" placeholder="Start writing...">
</textarea>

<div>
  <p>Word count: <span id="wordCount">0</span></p>
  <p>Character count: <span id="charCount">0</span></p>
  <p>Line count: <span id="lineCount">0</span></p>
  <p>Most repeated word: <span id="mostRepeatedWord"></span> (<span id="mostRepeatedWordCount">0</span> occurrences)</p>
</div>

You can see a textarea element, where the user can write their text, and a div element, where the application shows statistical data about the inserted text, such as word count, characters, lines, and the most repeated word. Remember that this data is shown in real time, while the user is writing.

The relevant JavaScript code extracting and displaying the statistical data is shown below:

const text = document.getElementById("text");
const wordCount = document.getElementById("wordCount");
const charCount = document.getElementById("charCount");
const lineCount = document.getElementById("lineCount");
const mostRepeatedWord = document.getElementById("mostRepeatedWord");
const mostRepeatedWordCount = document.getElementById("mostRepeatedWordCount");

text.addEventListener("keyup", ()=> {
  const currentText = text.value;
  
  wordCount.innerText = countWords(currentText);
  charCount.innerText = countChars(currentText);
  lineCount.innerText = countLines(currentText);
  let mostRepeatedWordInfo = findMostRepeatedWord(currentText);
  mostRepeatedWord.innerText = mostRepeatedWordInfo.mostRepeatedWord;
  mostRepeatedWordCount.innerText = mostRepeatedWordInfo.mostRepeatedWordCount;
});

Here you can see a block of statements getting the various DOM elements involved in displaying data and an event listener catching this data when the user finishes pressing each key.

Inside the body of the keyup event listener you find a few calls to the functions performing the actual data analysis: countWords(), countChars(), countLines() and findMostRepeatedWord(). You can find the implementation of these functions and the whole implementation of the text analyzer on CodePen.

Performance issues with the single thread

By analyzing the source code of this simple text analyzer application, you can see that the statistical extraction is performed each time the user finishes pressing a key on their keyboard. Of course, the computing effort related to the data extraction depends on the length of the text, so you might have a loss of performance while the text size grows.

Consider that the text analysis functions taken into account by this example are very easy, but you might want to extract more complex data such as keywords and their relevance, word classification, sentence length average and so on. Even if with a short or medium-length text this application might perform well, you could experience a loss of performance and getting the application to become unresponsive with a long text, especially when it is executed in a low-performance device, such as a smartphone.

Web Workers basics

The single-threaded processing model is intrinsic in the JavaScript language specification and it is applied both on the browser and on the server. To overcome this language restriction, the HTML5 specifications introduced the worker concept, that is an object providing a way to execute JavaScript code in a separate thread.

Creating a worker is straightforward: all you need is to isolate the code you want to execute in a separate thread in a file and create a worker object by invoking the *Worker()*constructor, as shown by the following example:

const myWorker = new Worker(“myWorkerCode.js”);

This type of worker is known as a Web Worker (another type of worker is the Service worker, but it is out of the scope of this article).

The interaction between the main thread and the worker’s thread is based on a message exchange system. Both the main thread and the worker’s thread can send messages by using the postMessage() method and receive messages by handling the message event.

For example, the main thread may start the worker’s thread by sending a message like this :

myWorker.postMessage(“start”);

As you can see, we passed the*start>*string as an argument for postMessage(), but you can pass whatever you want. It depends on you and on what your Web Worker expects but, remember, you cannot pass functions. Keep in mind, however, that data is passed by value. So, if you pass an object it will be cloned and any changes the worker makes on it will not affect the original object.

The worker receives the message by implementing a listener for the message event, as shown below:

self.addEventListener(“message”, (event) => {
  if (event.data === “start”) {
    //do things
  }
});

You can notice the self keyword. It refers to the current worker context, that is different from the global context of the main thread. You can also use the this keyword to refer to the worker context but, by convention, self is generally preferred.

So, in the example above, you attach the event listener to the current worker context and access the data coming from the main thread through the event.data property.

In the same way, the worker can send messages to the main thread by using postMessage():

self.postMessage(“ok”);

and the main thread receives messages by handling the message event, like this:

myWorker.addEventListener(“message”, (event) => {
  if (event.data === “ok”) {
    //do things
  }
});

Note that a worker may create another worker and communicate with it, so the interaction is not restricted to a worker and the main thread.

Finally, you can explicitly stop a worker in two ways: from inside the worker itself by invoking self.close() and from the calling thread by using the terminate() method, like in the following example:

myWorker.terminate();

A Web Worker for the text analyzer

After exploring the basics of Web Workers, let’s apply them to our application.

First, let’s extract the code to put in a separate file named textAnalyzer.js. You can take the opportunity to refactor the code by defining a function analyze() and returning the result of the text analysis, as shown here:

function analyze(str) {
  const mostRepeatedWordInfo = findMostRepeatedWord(str);
  
  return {
    wordCount: countWords(str),
    charCount: countChars(str),
    lineCount: countLines(str),
    mostRepeatedWord: mostRepeatedWordInfo.mostRepeatedWord,
    mostRepeatedWordCount: mostRepeatedWordInfo.mostRepeatedWordCount
  };
}

The other functions, countWords(), countChars() and so on, are defined in the same textAnalyzer.js file.

In the same file, we need to handle the message event in order to interact with the main thread. The following is the needed code:

self.addEventListener("message", (event) => {
  postMessage(analyze(event.data));
});

The event listener expects the text to be analyzed in the data property of the event object. Its only task is to simply return via postMessage() the result of applying the analyze() function to the text.

Now, the JavaScript code in the main script becomes as follows:

const text = document.getElementById("text");
const wordCount = document.getElementById("wordCount");
const charCount = document.getElementById("charCount");
const lineCount = document.getElementById("lineCount");
const mostRepeatedWord = document.getElementById("mostRepeatedWord");
const mostRepeatedWordCount = document.getElementById("mostRepeatedWordCount");

const textAnalyzer = new Worker("textAnalyzer.js");

text.addEventListener("keyup", ()=> {
  textAnalyzer.postMessage(text.value);  
});

textAnalyzer.addEventListener("message", (event) => {
  const textData = event.data;
  
  wordCount.innerText = textData.wordCount;
  charCount.innerText = textData.charCount;
  lineCount.innerText = textData.lineCount;
  mostRepeatedWord.innerText = textData.mostRepeatedWord;
  mostRepeatedWordCount.innerText = textData.mostRepeatedWordCount;
});

As you can see, we created the textAnalyzer Web Worker based on the textAnalyzer.js file.

Each time the user enters a key, a message is sent to the worker via postMessage() with the full text. The response from the worker comes from event.data in the form of an object, whose property values are assigned to the respective DOM elements for displaying.

Since the Web Worker’s code is executed in a separate thread, the user can continue inserting new text while the text analysis is in progress, without experiencing unresponsiveness.

Handling errors

What happens if an error occurs during the worker execution? In this case, an error event is fired and you should handle it in the calling thread through a normal event listener.

Suppose, for example, that our text analyzer worker checks if the data passed in the message is actually a text, like in the following code:

self.addEventListener("message", (event) => {
  if (typeof event.data === "string") {
    postMessage(analyze(event.data));    
  } else {
    throw new Error("Unable to analyze non-string data");
  }
});

The listener ensures that the passed data is a string before analyzing it and sending the message to the main thread. If the passed data is not a text, an exception is thrown.

On the main thread side, you should handle this exception by implementing a listener for the error event, as shown below:

textAnalyzer.addEventListener("error", (error) => {
  console.log(`Error "${error.message}" occurred in the file ${error.filename} at line ${error.lineno}`);
});

The event handler receives an error object with a few data about what went wrong. In the example we used:

  • the message property describes the error that occurred,
  • the filename property reports the name of the script file implementing the worker
  • the lineno property contains the line number where the error occurred

You can find the complete code of this implementation by following this link.

Web Workers restrictions

I hope you agree that Web Workers are amazing and very simple to use: you just need to use plain JavaScript and standard event handling for interoperation between the threads. Nothing particularly strange or complicated.

However, keep in mind that Web Workers have a few restrictions:

  • They cannot access the DOM either the window or the document objects. So, for example, don’t try to use console.log() to print messages on the browser’s console. This limitation along with passing serialized message data is necessary to make Web Workers thread-safe. It may seem too restrictive at first glance but, actually, this limitation guides you into a better separation of concerns and once you’ve learned how to deal with workers, the benefits will be clear.
  • In addition, Web Workers run only if the application’s files are served via HTTP or HTTPS protocol. In other words, they don’t run if your page is loaded from your local file system via file:// protocol.
  • Finally, the same origin policy also applies to Web Workers. This means that the script implementing the worker must be served from the same domain, including protocol and port, as the calling script.

Shared worker

As said before, Web Workers are used to implement expensive processing tasks in order to distribute the computational load. Sometimes the Web Worker may require a significant amount of resources, such as memory or local storage. When multiple pages or frames from the same application are opened, these resources are duplicated for each instance of the Web Worker. If the logic of your worker allows it, you could avoid growing resource requests by sharing the Web worker among multiple browser contexts.

Shared workers can help you. They are a variant of Web Workers we’ve seen so far. In order to distinguish this variant type from the previous ones, the latter are often called Dedicated workers.

Let’s take a look at how you can create a Shared worker by transforming our text analyzer.

The first step is to use the SharedWorker() constructor instead of Worker():

const textAnalyzer = new SharedWorker("textAnalyzer.js");

This constructor creates a proxy for the worker. Since the worker will communicate with multiple callers, the proxy will have a dedicated port that must be used to attach listeners and to send messages. So, you need to attach the listener for the message event as follows:

textAnalyzer.port.addEventListener("message", (event) => {
  const textData = event.data;
  
  wordCount.innerText = textData.wordCount;
  charCount.innerText = textData.charCount;
  lineCount.innerText = textData.lineCount;
  mostRepeatedWord.innerText = textData.mostRepeatedWord;
  mostRepeatedWordCount.innerText = textData.mostRepeatedWordCount;
});

Notice that the only difference is the use of the port property for attaching the event listener. In the same way, you need to use the port property to send a message via postMessage():

text.addEventListener("keyup", ()=> {
  textAnalyzer.port.postMessage(text.value);
});

Unlike before, however, you need to explicitly connect your thread to the worker thread by calling the start() method, as shown below:

textAnalyzer.port.start();

This is required to make sure that ports don’t dispatch events until the listener has been added. Keep in mind, however, that you don’t need to invoke start() if you attach your listener to the onmessage property instead of using addEventListener(), like this:

textAnalyzer.port.onmessage = (event) => {
  const textData = event.data;
  
  wordCount.innerText = textData.wordCount;
  charCount.innerText = textData.charCount;
  lineCount.innerText = textData.lineCount;
  mostRepeatedWord.innerText = textData.mostRepeatedWord;
  mostRepeatedWordCount.innerText = textData.mostRepeatedWordCount;
};

On the worker side, you need to arrange a bit the worker set up by replacing the message event listener with the following code:

self.addEventListener("connect", (event) => {
  const port = event.ports[0];

  port.addEventListener("message", (event) => {
    if (typeof event.data === "string") {
      port.postMessage(analyze(event.data));    
    } else {
      throw new Error("Unable to analyze non-string data");
    }
  });

  port.start();
});

You added a listener for the connect event. This event fires when a caller invokes the start() method of the worker proxy’s port or when it attaches an event listener to the onmessage property. In both cases, a port is assigned to the worker and you can get it by accessing the first element of the ports array of the event object. Similar to the caller, you need to use this port to attach event listeners and send messages. In addition, if you used addEventListener() to attach your listener, you need to establish a connection with the caller through the port.start() method.

Now your worker has become a shared worker.

The full code for this implementation is available at this link.

Conclusion

In this article, we discussed the limitations that the JavaScript single-threaded processing model may have in some scenarios. The implementation of a simple real-time text analyzer tried to better explain the issue.

Web Workers were introduced to solve the potential performance issues. They were used to spawn in a separate thread. We discussed the Web Workers restrictions and finally explained how to create shared workers when we need to share a Web Worker among multiple pages or frames.

You can find the final code of the workers created in this article in this GitHub repository.

#javascript #web-development #web-service #html #html5