---
title: useWebWorker composable
related:
  - title: useWebWorkerFn composable
    path: use-web-worker-fn.md
---
The `useWebWorker()` composable connects a component to a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API): 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](use-web-worker-fn.md) is the better fit.

> [!NOTE]
> 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.

> [!TIP]
> **Outside of a component**
>
> 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

```js
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
        // ...
      }
    }
  )

  // ...
}
```

```ts
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 `URL` object); the worker gets created with the native `type`, `name` and `credentials` options, and `type` defaults to `'module'` (set it to `'classic'` for a script that relies on `importScripts()`)
- a `Worker` instance you already created
- a function returning a `Worker`; the default export of a Vite `?worker` import is such a function, and so is an arrow function wrapping `new 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 `?worker` constructor picks up the `name` you 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:

```js
// 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)
}
```

```js
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:

```js
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](use-object-url.md) 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.

Example "Basic":

```vue
<template>
  <div class="row items-center q-gutter-sm q-mb-md">
    <q-input
      v-model.number="count"
      type="number"
      dense
      outlined
      label="Primes to find"
      style="width: 160px"
    />
    <q-btn
      color="primary"
      label="postWorkerMessage()"
      no-caps
      @click="postWorkerMessage({ count })"
    />
    <q-btn
      v-if="workerStatus === 'running'"
      color="negative"
      label="terminateWorker()"
      no-caps
      @click="terminateWorker"
    />
  </div>

  <div v-if="workerStatus === 'idle'"
    >No worker running (the next postWorkerMessage creates one)</div
  >
  <div v-else-if="workerData === null">No message received yet</div>
  <div v-else>
    Largest prime among the first {{ workerData.count }}:
    {{ workerData.largest }} (computed in {{ workerData.ms }}ms)
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useObjectUrl, useWebWorker } from 'quasar'

// In your app the worker lives in its own file:
//   () => new Worker(new URL('./primes.js', import.meta.url), { type: 'module' })
// The example inlines the script so that it fits in one file.
const script = `
onmessage = ({ data }) => {
  const start = performance.now()
  const primes = []
  for (let n = 2; primes.length < data.count; n++) {
    if (primes.every(p => n % p !== 0)) primes.push(n)
  }
  postMessage({
    count: data.count,
    largest: primes[primes.length - 1],
    ms: Math.round(performance.now() - start)
  })
}`

const count = ref(2000)

// revoked when the component gets destroyed
const { objectUrl } = useObjectUrl(
  new Blob([script], { type: 'text/javascript' })
)

const { workerStatus, workerData, postWorkerMessage, terminateWorker } =
  useWebWorker(() => new Worker(objectUrl.value), { lazy: true })
</script>
```

> [!TIP]
> **Content Security Policy**
>
> 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](use-web-worker-fn.md) do.
