The useWebWorker() composable connects a component to a Web Worker: it creates the worker from a script you wrote, exposes the last message received as a reactive value, lets you post messages (with a transfer list) and terminates the worker when the component gets destroyed.
Use it for a long-lived worker with its own protocol (a parser fed with chunks, a search index, a simulation that streams progress). To simply run one function off the main thread and await its result, useWebWorkerFn is the better fit.
On the server-side of SSR or SSG modes, no worker gets created: workerStatus stays idle, postWorkerMessage() does nothing and no message ever arrives. The worker is created on the client once the component is mounted, so the status is idle before hydration too.
The composable can also be called outside of setup(): in a boot file, a store or a plain module. There is no mount to wait for there, so the worker is created right away (unless lazy is set) and nothing terminates it by itself: call terminateWorker() when you are done.
Syntax
import { useWebWorker } from 'quasar'
setup () {
const {
workerStatus,
workerData,
workerError,
postWorkerMessage,
terminateWorker
} = useWebWorker(
source,
{
// all optional:
lazy: true, // do not create the worker on mount (default: false);
// the first postWorkerMessage() does it
// the native Worker options, used when "source" is a URL:
type: 'classic', // 'module' or 'classic' (default: 'module')
name: 'primes', // labels the worker in the devtools (default: none)
credentials: 'include', // for a module worker script (default: 'same-origin')
onMessage (data, evt) { // called with each message from the worker
// ...
},
onError (evt) { // called with the "error" / "messageerror" events
// ...
},
onCreate (worker) { // called with each Worker created
// ...
},
onTerminate (worker, reason) { // called after a worker got killed
// ...
}
}
)
// ...
}function useWebWorker<Data = any>(
source:
| string
| URL
| Worker
| ((options?: WorkerOptions) => Worker)
| (new (options?: WorkerOptions) => Worker),
options?: WorkerOptions & {
lazy?: boolean
onMessage?: (data: Data, evt: MessageEvent<Data>) => void
onError?: (evt: ErrorEvent | MessageEvent) => void
onCreate?: (worker: Worker) => void
onTerminate?: (worker: Worker, reason: 'terminate' | 'unmount') => void
}
): {
workerStatus: Ref<'idle' | 'running' | 'terminated'>
workerData: ShallowRef<Data | null>
workerError: ShallowRef<ErrorEvent | MessageEvent | null>
postWorkerMessage: (message: any, transfer?: Transferable[]) => void
terminateWorker: () => void
}The source can be:
- the URL of the worker script (a String or a
URLobject); the worker gets created with the nativetype,nameandcredentialsoptions, andtypedefaults to'module'(set it to'classic'for a script that relies onimportScripts()) - a
Workerinstance you already created - a function returning a
Worker; the default export of a Vite?workerimport is such a function, and so is an arrow function wrappingnew Worker(new URL('./worker.js', import.meta.url), { type: 'module' }), which keeps the script statically analyzable by the bundler; the function receives the native options as its argument, so a?workerconstructor picks up thenameyou set
The worker gets created when the component is mounted (or right away, when the composable is used outside of a component), so a worker that sends messages on its own (a script that boots a WASM module and reports when ready, a ticker) is up from the start. Set lazy: true for a worker that should wait for your first postWorkerMessage() instead: a component that never talks to it then never starts a thread. Either way the worker is terminated when the component gets destroyed, whatever form the source took. workerStatus follows this lifecycle: idle while there is no worker (on the server and on the client alike, so markup that depends on it hydrates without a mismatch), running while it is alive and terminated once the component got destroyed.
workerData holds the data of the last message received from the worker and workerError the last error (the worker threw or failed to load) or messageerror (a message could not be deserialized) event. The onMessage and onError hooks get called in the same situations, so you do not need to watch the refs. An error of the worker is still reported to the console as usual; call evt.preventDefault() in onError if you handled it.
postWorkerMessage(message, transfer) sends a message to the worker (creating it first when there is none), moving the objects in the optional transfer list (an ArrayBuffer, a MessagePort, an ImageBitmap…) instead of copying them. It does nothing once the component got destroyed.
terminateWorker() kills the worker and puts workerStatus back to idle: the next postWorkerMessage() creates a new worker from the source (the mount-time creation does not happen again). Use it to free the thread when a job is done or to abort one that runs too long, then talk to the worker again whenever you need it. The exception is a Worker instance passed as source: it cannot be created again, so terminateWorker() is final for it and workerStatus becomes terminated.
onCreate(worker) gets called with each Worker the composable starts using, so it is the place to send a setup message (a configuration, a MessagePort) that every fresh worker needs. onTerminate(worker, reason) gets called right after a worker got killed, with reason set to 'terminate' for a terminateWorker() call or 'unmount' for the component being destroyed; reject pending requests or reset progress state there.
Writing the worker
With Quasar CLI (Vite), the worker script is a file of your own that you point to with new URL() so that it gets bundled:
// src/workers/primes.js
onmessage = ({ data }) => {
const primes = []
for (let n = 2; primes.length < data.count; n++) {
if (primes.every(p => n % p !== 0)) primes.push(n)
}
postMessage(primes)
}import { useWebWorker } from 'quasar'
setup () {
const { workerData, postWorkerMessage } = useWebWorker(
() => new Worker(new URL('../workers/primes.js', import.meta.url), { type: 'module' })
)
function compute () {
postWorkerMessage({ count: 1000 })
}
// ...
}The ?worker import form works too:
import PrimesWorker from '../workers/primes.js?worker'
const { workerData, postWorkerMessage } = useWebWorker(PrimesWorker)Example
The example below builds its worker from an inline script (through a Blob URL that useObjectUrl revokes when the component gets destroyed) so that everything fits in one file. In your app you would rather keep the worker in its own file, as shown above.
A worker script must be allowed by the worker-src directive of your CSP (which falls back to script-src). Add blob: to it when creating workers from Blob URLs, as the example above and useWebWorkerFn do.