HTML5 Web Workers are the web’s answer to multi-threading. They’ve been around for a while now, so are pretty safe to rely on. A Worker is typically initialised by instantiating Worker
with the URL of your ‘worker script’:
var myWorker = new Worker('path/to/my/worker.js'); |
And then you’d interface with the worker via asynchronous message-passing:
// == Within parent page == myWorker.postMessage('Did you receive this?'); myWorker.onmessage = function() { if (e.data === 'I sure did, friend.') { myWorker.postMessage('Cool, I am gonna send you some data, k?'); } // etc. } // == Within worker == self.onmessage = function(e) { if (e.data === 'Did you receive this?') { self.postMessage('I sure did, friend.'); } // etc. }; |
The message-passing code I tend to see in the wild is typically un-abstracted and chaotic. I felt there was some things worth improving in this area so I created operative.
Operative allows you to define worker code inline without having to create specific files and load them separately at runtime. With operative it’s as simple as:
var calculator = operative({ // Any methods defined here are executed within a worker! doComplexThing: function(a, b, c) { // do stuff. return result; } }); // Call method, passing a callback as the last arg: calculator.doComplexThing(1, 2, 3, function(result) { result; // => value returned from within worker }); |
Additional benefits include:
- Debuggability (Blob + Chromium Web Inspector = Pure Bliss)
- Console debug methods for free (log, warn, debug etc.)
- Degradability / Progressive Enhancement (It works with no/partial worker support)
Operative’s Degradability
Operative degrades in this order:
(higher is better/cooler)
- Full Worker via Blob & Structured-Cloning (Ch13+, FF8+, IE11+, Op11.5+, Sf5.1+)
- Full Worker via Eval & Structured-Cloning (IE10)
- Full Worker via Blob & JSON marshalling (???)
- Full Worker via Eval & JSON marshalling (Sf4)
- No Worker: Regular JS called inline (older browsers = slow)
Operative will degrade in environments with no Worker support. In such a case the code would execute as regular in-place JavaScript. The calls will still be asynchronous though, not immediate.
Leaky but worth it?
The abstraction that operative offers is somewhat leaky. I see this is an acceptable leakiness. I would hope that anyone using operative understands what’s really happening behind the scenes.
One particular leak is the lack of the native worker APIs, such as importScripts
, in the fully-degraded state which runs JavaScript in-place. If you’re just aiming for newer browsers then you can use importScripts
to your heart’s content though.
Other leaks are the subtle differences you could encounter between structured-cloning and the degraded JSON-transfer approach. E.g. one understands /regexes/
and the other doesn’t. Operative uses JSON stringify/parse where structured-cloning is unavailable.
Basically, short of creating a new DSL with severely limited possibilities, I very much doubt it’s even possible to create a non-leaky worker abstraction. And I think that’s ok. Operative isn’t perfection. It’s just a tad better and a tad more seamless than the conventional way of utilising workers… And that is sufficient benefit for me to feel okay with using it.
Other options
I wasn’t even going to make operative degradable until Calvin Metcalf mentioned his similar-in-principle communistjs library which does degrade. Seeing it as a possibility I decided to implement JSON-transfer fallbacks and an eval option so that IE10 (the browser of rubbish worker/blob support) could be used.
I suggest checking out both communistjs library and operative if you’re looking to abstract away Worker awkwardness.
Also, watch out, operative is new and potentially buggy. It’s also a bit of an experiment. I’m not even sure this kind of thing is worth abstracting.
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!