56
edits
Changes
→Shared Worker
== Shared Worker ==
Shared Worker is a kind of Web Worker that allows multiple scripts (e.i.: tabs, windows, iframes or other workers) to access and share the worker's resources simultaneously among them. The Shared Worker implements a different interface compared to the Dedicated Worker and the process on which it runs also has a different global scope - the SharedWorkerGlobalScope.
=== [https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker The SharedWorker Interface - The Main Processes] ===
The SharedWorker interface is used in the process that would spawn one or many shared worker processes.
==== Creating a Shared Worker ====
The SharedWorker interface derives from the AbstractWorker interface.
To create an instance of a Shared Worker, we use the SharedWorker's constructor. The constructor receives, as the first parameter, a URL to a JavaScript source file that would be the starting point of the process. It also takes, as an optional 2nd parameter, an option object.
<pre>
let worker = new SharedWorker('url-to-worker.js', options);
</pre>
What happens at the construction-time of the Shared Worker is that the browser will start-up a new process, load the JavaScript source file as the starting point and return an instance of that process with a port object if no process associated with that JavaScript source file has been created before. In the case that an existing process associated with the JavaScript source file is found, the browser simply returns that process instance with a new port object.
==== Send Message to the Shared Worker ====
As a Shared Worker is created, the returned worker object has a read-only property of type ''[https://developer.mozilla.org/en-US/docs/Web/API/MessagePort MessagePort]'', namely ''port''. Different main processes instantiating a new Shared Worker with the same JavaScript source file would have different port objects representing different connections.
The port object is used to communicate with the Shared Worker and control the connection with three methods demonstrated below. The following code snippet creates a shared worker, initiates communication with it and sends 3 separated messages which are a string, a complex object and another string in that order. Finally, it terminates the communication with the worker.
<pre>
// Instantiate a Shared Worker
let worker = new SharedWorker('sharedWorker.js');
// Initiate the communication
worker.port.start();
// Send 3 messages to the shared worker
worker.port.postMessage("Hello worker from the main process!");
worker.port.postMessage({ foo: 3, bar: "dps921", foobar: [1, 2, 4] });
worker.port.postMessage("Goodbye!");
// Terminate the communication
worker.port.close();
</pre>
Notice from the code that it is different from the Dedicated Worker where the communication was done via the worker itself, the Shared Worker has to communicate through a port because (in the next section, we will see that) a Shared Worker can communicate with multiple processes at once.
==== Receive Message from the Shared Worker ====
Besides sending, the port object can also be used to receive message sent from the worker via the onmessage event handler. The handler can be set directly as in the following snippet.
<pre>
// Instantiate a Shared Worker
let worker = new SharedWorker('sharedWorker.js');
// Initiate the communication and listen to any communication from the worker
worker.port.onmessage = function(event) {
let data = event.data;
console.log(data);
};
</pre>
The snipppet above instantiates a shared worker and starts communication by registering a function to be executed when a message is received. The content of the message can be accessed in the data attribute of the event object which is passed in as the first parameter of the function.
Notice from the code that the calls to the start() method of the port are omitted. This is because the start() method is implicitly called when the onmessage handler is set. Also, the call to the close() method also no longer exists and that is because if close() is called, the onmessage event handler is also implicitly unregistered.
=== [https://developer.mozilla.org/en-US/docs/Web/API/SharedWorkerGlobalScope The SharedWorkerGlobalScope - The Worker] ===
The SharedWorkerGlobalScope is the global scope of the JavaScript source file that is loaded by the SharedWorker() constructor. Unlike the regular browser's global scope which is accessible via the ''window'' object, the SharedWorkerGlobalScope is represented by the ""self"" object.
An important feature of the Shared Worker is that all processes connected to the worker share the same global scope as there is only a single worker. This enables data-sharing between multiple processes which will be demonstrated later in this section.
==== Detecting a New Connection ====
When a new connection is established between a process and a shared worker, an event handler in the SharedWorkerGlobalScope, namely ''onconnect'' is triggered. By registering a function to this handler, we can execute custom code when this happens. Let's say we have the following snippet in the sharedWorker.js source file:
<pre>
// self.onconnect = ...
onconnect = function(event) {
let port = event.ports[0];
port.start();
port.postMessage("Hello main process from worker!");
port.postMessage("Goodbye!");
port.close();
}
</pre>
The sharedWorker.js file above, when loaded into a worker, would initiate the communication, send 2 message and end the communication for any process that connects to it. If that process had the onmessage event handler set up, they can access this message from the event.data attribute.
Notice on the code that the port of the connecting process can be accessed in the even.ports[0]. It is always the case that this ports array is going have exactly 1 element in the onconnect event handler. Besides that, same as in the main process, the start() and close() method calls on the port object are necessary when we do not set a onmessage event handler for it.
==== Communicating with Connected Processes ====
As the connected ports are accessible in the sharedWorker.js, specifically in the onconnect event handler, we can set them up so that each of them can listen for messages sent from their corresponding processes.
The following sharedWorker.js source file will use an array to store all the ports of the connected processes. Each of the port is set up to receive messages from its own process and, then, broadcast the messages to all other processes.
<pre>
let allConnectedPorts = [];
onconnect = function(event) {
let port = event.ports[0];
port.onmessage = function(e) {
allConnectedPorts.forEach(p => p.postMessage(e.data));
};
allConnectedPorts.push(port);
}
</pre>
=== An Interactive Demo - Battleships Game ===
With the Shared Worker and a few techniques above, we can set up an application fully works on client-side (i.e.: the browser) and is capable of sharing data between windows.
I have leveraged this knowledge to complete a demo of a 2-player Battleships game that consists of 2 browser windows - 1 for each player. Rather than having a server coordiating the 2 players, the players would coordinate themselves by sending/receiving messages to/from each other through a shared worker acting as an arbitrator.
The demo is accessible here or through the following URL:
<mark>
https://vitokhangnguyen.github.io/WebWorkerDemo/shared.html
</mark>
= References =