Skip to content

Instantly share code, notes, and snippets.

@estebanz01
Last active March 23, 2024 23:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save estebanz01/5d90643a4865c5f2798cc74e201bdbb1 to your computer and use it in GitHub Desktop.
Save estebanz01/5d90643a4865c5f2798cc74e201bdbb1 to your computer and use it in GitHub Desktop.
A chat with webworkers in it.
/*
* CSS chat
*/
body {
top: 0;
left: 0;
background-color: #caccd1;
margin-left: 35%;
margin-right: 35%;
padding-bottom: 5em;
border-bottom-style: solid;
}
header {
margin-top: 3em;
}
small {
display: block;
font-style: italic;
}
.sent-by-me, .sent-by-worker {
border-style: groove;
border-radius: 10pt;
text-align: center;
vertical-align: center;
word-wrap: break-word;
width: 50%;
height: auto;
padding: 1em;
font-size: 11pt;
}
.sent-by-me {
margin-left: 11em;
background-color: #dcf8c6;
}
.sent-by-worker {
margin-left: 1em;
background-color: #ece5dd;
}
<!doctype HTML>
<html lang="en">
<head>
<title>The best chat ever</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="chat.css">
<script src="chat.js"></script>
</head>
<body>
<header>
<h1>The best chat you will ever see</h1>
<small>In a educational context</small>
<h2 id="me-id">Me</h2>
</header>
<section id="messages">
<div id="chatbox">
</div>
</section>
<section>
<div id="message-box">
<textarea id="me-message"></textarea>
<button id="send-message">send message!</button>
<button id="end-chat">end chat!</button>
</div>
</section>
<script>
const chat = new Chat();
document.querySelector('#send-message').addEventListener('click', (e) => {
const msg = document.querySelector('#me-message');
chat.sendMessage(msg.value);
msg.value = '';
});
document.querySelector('#end-chat').addEventListener('click', (e) => {
chat.end();
alert('chat finished');
document.querySelector('#me-message').disabled = true;
});
window.addEventListener('unload', (evt) => chat.end());
</script>
</body>
</html>
if (!window.Worker) {
throw 'Web workers not supported';
}
class Chat {
constructor() {
this._id = this._guid();
this.worker = new SharedWorker('./worker.js');
this.chatBox = document.querySelector('#chatbox');
this._bindWorkerListeners();
document.querySelector('#me-id').innerText += ` ${this._id}`;
}
sendMessage(message) {
this.chatBox.appendChild(this._formatMessage(message, true));
this.worker.port.postMessage({ text: message, user: this._id });
}
end() {
if (this.worker != null) {
this.worker.port.postMessage({
text: `User ${this._id} is offline.`,
user: this._id,
info: true
});
this._formatInfo('You are offline');
this.worker.port.close();
this.worker = null;
}
}
_formatMessage(msg, sentByMe = false) {
let p = document.createElement('p');
p.innerText = msg;
if (sentByMe) {
p.classList.add('sent-by-me');
} else {
p.classList.add('sent-by-worker');
}
return p;
}
_formatInfo(msg) {
let small = document.createElement('small');
small.innerText = msg;
return small;
}
_bindWorkerListeners() {
this.worker.port.onmessageerror = (evt) => {
console.error('error worker', evt);
};
this.worker.onerror = (evt) => {
console.error('error worker', evt);
};
// If we want to use port.addEventListener
// we would also need to do port.start()
this.worker.port.onmessage = (evt) => {
if (evt.data.user != this._id) {
let msg = null;
if (evt.data.info) {
msg = this._formatInfo(evt.data.text);
} else {
msg = this._formatMessage(evt.data.text);
}
this.chatBox.appendChild(msg);
}
};
}
/**
* Generates a GUID string.
* @returns {String} The generated GUID.
* @example af8a8416-6e18-a307-bd9c-f2c947bbb3aa
* @author Slavik Meltser (slavik@meltser.info).
* @link https://slavik.meltser.info/?p=142
*/
_guid() {
function _p8(s) {
var p = (Math.random().toString(16)+"000000000").substr(2,8);
return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
}
return _p8() + _p8(true) + _p8(true) + _p8();
}
}
<h1 id="web-workers">Web Workers</h1>
<h2 id="tldr-tldh">TL;DR (TL;DH)</h2>
<blockquote>
<p>Web Workers are a simple means for web content to run scripts in background threads.<br />
The worker thread can perform tasks without interfering with the user interface.</p>
</blockquote>
<h2 id="what-is-a-web-worker">What is a Web Worker?</h2>
<p>A web worker is just a javascript <em>context</em> (i.e. webpage subprocess) that runs inside the browser<br />
main process, without blocking other javascript <em>contexts</em>.</p>
<p>Those contexts can interact with other contexts via <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API"><em>Web Workers API</em></a>.</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> __________________________________________________________
<span class="op">|</span>BROWSER MAIN PROCESS <span class="op">|</span>
<span class="op">|</span> _________________________________________________ <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span>WEBPAGE PROCESS <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span> _____________ _____________ <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span> <span class="op">|</span> <span class="op">|</span> <span class="op">|</span> <span class="op">|</span> <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span> <span class="op">|</span> JS context <span class="op">|</span> [WW Api] <span class="op">|</span> JS context <span class="op">|</span> <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span> <span class="op">|</span> (thread) <span class="op">|</span> <span class="op">&lt;------&gt;</span> <span class="op">|</span> (thread) <span class="op">|</span> <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span> <span class="op">|</span>_____________<span class="op">|</span> <span class="op">|</span>_____________<span class="op">|</span> <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span> <span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span>_________________________________________________<span class="op">|</span> <span class="op">|</span>
<span class="op">|</span> <span class="op">|</span>
<span class="op">|</span>__________________________________________________________<span class="op">|</span></code></pre></div>
<p>The web workers in any of their flavors are thread-safe, meaning that it's pretty weird to have<br />
concurrency problems between two javascript <em>contexts</em>.</p>
<p>In order to have two javascript <em>contexts</em> to communicate between them, certain requirements needs<br />
to be met (<em>in the web development land</em>):</p>
<ul>
<li>One javascript context must act as sender and the other as receiver.</li>
<li>All js receivers must be within the same origin as the js sender or diff origin must allow external access (CORS madness).</li>
<li>A receiver can be used by more than one js sender, as long as the js receiver code allows it (SharedWebWorker).</li>
<li>At least one sender must be created by a webpage subprocess.</li>
<li>A shared worker <strong>must</strong> share the same origin (i.e. protocol, host, port) as the sender (Diff flavor of CORS madness).</li>
</ul>
<h2 id="code-examples">Code examples</h2>
<h3 id="dedicated-web-workers.">Dedicated web workers.</h3>
<p>Here's the javascript code that is istantiated by a webpage.</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">// main.js</span>
<span class="cf">if</span> (<span class="op">!</span><span class="va">window</span>.<span class="at">Worker</span>) <span class="op">{</span>
<span class="cf">throw</span> <span class="st">&#39;Web workers not supported&#39;</span><span class="op">;</span>
<span class="op">}</span>
<span class="co">// Let&#39;s create a dedicated web worker instance.</span>
<span class="kw">const</span> myWorker <span class="op">=</span> <span class="kw">new</span> <span class="at">Worker</span>(<span class="st">&#39;a-worker.js&#39;</span>)<span class="op">;</span>
<span class="co">// When I click the button</span>
<span class="va">document</span>.<span class="at">querySelector</span>(<span class="st">&#39;#clickme&#39;</span>).<span class="at">addEventListener</span>(<span class="st">&#39;click&#39;</span><span class="op">,</span> (evt) <span class="op">=&gt;</span> <span class="op">{</span>
<span class="co">// Send the event object that is created for this listener to the web worker.</span>
<span class="co">// The type of the data that we can send is any javascript object or value that can be</span>
<span class="co">// deep cloned (see list here: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).</span>
<span class="va">myWorker</span>.<span class="at">postMessage</span>(<span class="op">{</span> <span class="dt">argument</span><span class="op">:</span> evt <span class="op">}</span>)<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="co">// everytime the worker wants to speak to us.</span>
<span class="va">myWorker</span>.<span class="at">onMessage</span> <span class="op">=</span> (evt) <span class="op">=&gt;</span> <span class="op">{</span>
<span class="at">alert</span>(<span class="va">evt</span>.<span class="at">data</span>)<span class="op">;</span>
<span class="va">console</span>.<span class="at">log</span>(<span class="st">&#39;an alert has been issued with the data received&#39;</span>)<span class="op">;</span>
<span class="op">};</span></code></pre></div>
<p>and here's the worker code:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="co">//a-worker.js</span>
<span class="co">// This is the same as DedicatedWorkerGlobalScope.onmessage</span>
onmessage <span class="op">=</span> (evt) <span class="op">=&gt;</span> <span class="op">{</span>
<span class="co">// We cannot use alert, because: 1. It&#39;s a background process. 2. alert == window.alert.</span>
<span class="va">console</span>.<span class="at">log</span>(<span class="st">&#39;The sender talked to us!&#39;</span>)<span class="op">;</span>
<span class="kw">let</span> theWords <span class="op">=</span> <span class="va">evt</span>.<span class="at">data</span><span class="op">;</span> <span class="co">// Since we sen an object, theWords should be a **copy** of that.</span>
<span class="va">console</span>.<span class="at">log</span>(<span class="st">&#39;the spoken words&#39;</span><span class="op">,</span> <span class="va">theWords</span>.<span class="at">argument</span>)<span class="op">;</span>
<span class="at">postMessage</span>(<span class="st">&#39;I CAN HEAR YOU FINE&#39;</span>)<span class="op">;</span>
<span class="op">};</span></code></pre></div>
<h3 id="shared-web-workers">Shared Web workers</h3>
<p>The code differs a bit from the dedicated web worker, in the sense that now we need to keep track<br />
of a <em>port</em> which is unique per shared connection, so let's see the &quot;live&quot; example.</p>

Web Workers

TL;DR (TL;DH)

Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface.

What is a Web Worker?

A web worker is just a javascript context (i.e. webpage subprocess) that runs inside the browser main process, without blocking other javascript contexts.

Those contexts can interact with other contexts via Web Workers API.

     __________________________________________________________
    |BROWSER MAIN PROCESS                                      |
    |   _________________________________________________      |
    |  |WEBPAGE PROCESS                                  |     |
    |  |  _____________              _____________       |     |
    |  | |             |            |             |      |     |
    |  | | JS context  |  [WW Api]  | JS context  |      |     |
    |  | |  (thread)   |  <------>  |  (thread)   |      |     |
    |  | |_____________|            |_____________|      |     |
    |  |                                                 |     |
    |  |_________________________________________________|     |
    |                                                          |
    |__________________________________________________________|

The web workers in any of their flavors are thread-safe, meaning that it's pretty weird to have concurrency problems between two javascript contexts.

In order to have two javascript contexts to communicate between them, certain requirements needs to be met (in the web development land):

  • One javascript context must act as sender and the other as receiver.
  • All js receivers must be within the same origin as the js sender or diff origin must allow external access (CORS madness).
  • A receiver can be used by more than one js sender, as long as the js receiver code allows it (SharedWebWorker).
  • At least one sender must be created by a webpage subprocess.
  • A shared worker must share the same origin (i.e. protocol, host, port) as the sender (Diff flavor of CORS madness).

Code examples

Dedicated web workers.

Here's the javascript code that is istantiated by a webpage.

// main.js
if (!window.Worker) {
  throw 'Web workers not supported';
}

// Let's create a dedicated web worker instance.
const myWorker = new Worker('a-worker.js');

// When I click the button
document.querySelector('#clickme').addEventListener('click', (evt) => {
  // Send the event object that is created for this listener to the web worker.
  // The type of the data that we can send is any javascript object or value that can be
  // deep cloned (see list here: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
  myWorker.postMessage({ argument: evt });
});

// everytime the worker wants to speak to us.
myWorker.onMessage = (evt) => {
  alert(evt.data);
  console.log('an alert has been issued with the data received');
};

and here's the worker code:

//a-worker.js

// This is the same as DedicatedWorkerGlobalScope.onmessage
onmessage = (evt) => {
  // We cannot use alert, because: 1. It's a background process. 2. alert == window.alert.
  console.log('The sender talked to us!');

  let theWords = evt.data; // Since we sen an object, theWords should be a **copy** of that.
  console.log('the spoken words', theWords.argument);

  postMessage('I CAN HEAR YOU FINE');
};

Shared Web workers

The code differs a bit from the dedicated web worker, in the sense that now we need to keep track of a port which is unique per shared connection, so let's see the "live" example.

var clients = [];
onconnect = (evt) => {
let client = evt.ports[0];
clients.push(client);
clients.forEach((port) => {
if (port != client) {
port.postMessage({ text: 'new user online.', info: true, id: null });
}
});
client.onmessage = (e) => {
if (e.data.info && e.data.id != null) {
clients = clients.filter((port) => port != client);
clients.forEach((port) => {
port.postMessage({ text: `User ${e.data.id} is offline`, info: true, id: null });
});
} else {
clients.forEach((port) => port.postMessage(e.data));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment