Interphone provides a realiable and predictable wrapper for effortless communication with web workers.
Web workers are incredibly useful for offloading work from the main thread. Their message-based API, however, can be very unpredictable (besides being quite tedious to master). Consider this code:
// [index.js]
const worker = new Worker('worker.js');
worker.onmessage = (result) => console.log(`The result is: ${result}`)
worker.postMessage([/* ... */]);
// [worker.js]
self.onmessage = ({ data }) => {
// Very compute-intensive operations
postMessage(result);
};
Seems fine. But what if we posted two messages? Three? Twenty? We would (hopefully 🤷♂️) receive two/three/twenty messages back, but the order might be incorrect, and we would not be able to tell which one corresponds to which of ours.
Interphone solves this by wrapping the worker in an asynchronous function, eliminating all the hassle of using postMessage
. With Interphone, the code above would become:
// [index.js]
import { loadWorker } from 'interphone/main';
const worker = loadWorker('worker.js');
worker([/* ... */]).then((result) => console.log(`The result is: ${result}`));
// [worker.js]
import { wrapHandler } from 'interphone/worker';
self.onmessage = wrapHandler(({ data }) => {
// Very compute-intensive operations
return result; // This can also be a promise
});
As you can see, by invoking the worker with Interphone, we can be sure that every call to the worker will give the correct result, no matter the order of calls or how long the computation takes.
import from
interphone/main
or justinterphone
Importing and using a worker is incredibly easy. Just load it with loadWorker
and you will have access to an asynchronous function that invokes your worker and returns as soon as the worker has finished. Let's see an example:
import { loadWorker } from 'interphone/main';
const concatenateWorker = loadWorker('concatenate-worker.js');
// You can now call `worker` just like any async function
concatenateWorker(['This', 'is', 'so', 'cool!'])
.then((concatenatedString) => console.log(concatenatedString));
import from
interphone/worker
or justinterphone
In your worker, it's even easier: just create a synchronous or asynchronous function that takes in the data as its only argument and returns the result. Then, wrap it with wrapHandler
and assign
the wrapped function to self.onmessage
. Again, an example (the concatenate-worker.js
from before):
import { wrapHandler } from 'interphone/worker';
const concatenateStrings = (strings) => strings.join(' '); // This could also be async
self.onmessage = wrapHandler(concatenateStrings);
If your worker happened to throw an error upon being called, it's no problem. The promise will be rejected, so you can handle the error as if it were just any asynchronous function throwing.
While Interphone is great when your web workers are just like functions (that is, you invoke them with some data/arguments and they return a single result), it's not that great when they are not function-like. For example, if your worker stayed in the background and emitted events every now and then, Interphone wouldn't come in very useful.
This work is licensed under a Creative Commons Attribution 4.0 International License.