---
title: useWebWorkerFn composable
related:
  - title: useWebWorker composable
    path: use-web-worker.md
---
The `useWebWorkerFn()` composable runs a function of yours in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), off the main thread, and hands you its result as a Promise. There is no worker file to write: the function itself becomes the worker script. Use it for a CPU-bound computation (sorting or filtering a large data set, parsing a big file, image processing, hashing) that would otherwise freeze the UI.

To drive a long-lived worker script of your own, with its own messages, see [useWebWorker](use-web-worker.md).

> [!NOTE]
> On the server-side of SSR or SSG modes, there are no workers: `runWorkerFn()` runs your function inline (in the same process) and resolves with its result, so a value computed during SSR is the same one the client would compute. Scripts listed in `dependencies` are not loaded there.

> [!TIP]
> **Outside of a component**
>
> The composable can also be called outside of `setup()`: in a boot file, a store or a plain module. Nothing terminates the worker by itself there: call `terminateWorkerFn()` when you are done.

## Syntax

```js
import { useWebWorkerFn } from 'quasar'

setup () {
  const { workerFnStatus, runWorkerFn, terminateWorkerFn } = useWebWorkerFn(
    (a, b) => a + b, // the function to run in the worker
    {
      // all optional:
      timeout: 10000,          // ms before a running call gets rejected (default: none, calls never time out)
      dependencies: [ /* ... */ ],      // script URLs the function needs (default: none)
      localDependencies: [ /* ... */ ], // your own functions it calls (default: none)
      transfer: (a, b) => [ /* ... */ ], // Transferables among the arguments (default: none, everything is cloned)

      onSuccess (result, args) { // called when a call resolves
        // ...
      },
      onError (error, args) { // called when a call rejects with an error
        // ...
      },
      onTimeout (args) { // called when a call hits the timeout
        // ...
      },
      onTerminate (reason) { // called right after the worker got killed
        // ...
      }
    }
  )

  // ...
}
```

```ts
function useWebWorkerFn<Fn extends (...args: any[]) => any>(
  fn: Fn,
  options?: {
    timeout?: number
    dependencies?: (string | URL)[]
    localDependencies?: Function[]
    transfer?: (...args: Parameters<Fn>) => Transferable[]
    onSuccess?: (result: Awaited<ReturnType<Fn>>, args: Parameters<Fn>) => void
    onError?: (error: unknown, args: Parameters<Fn>) => void
    onTimeout?: (args: Parameters<Fn>) => void
    onTerminate?: (
      reason: 'terminate' | 'timeout' | 'error' | 'unmount'
    ) => void
  }
): {
  workerFnStatus: Ref<'idle' | 'running' | 'success' | 'error' | 'timeout'>
  runWorkerFn: (...args: Parameters<Fn>) => Promise<Awaited<ReturnType<Fn>>>
  terminateWorkerFn: () => void
}
```

`runWorkerFn(...args)` calls your function with the arguments in the worker and resolves with what it returned (a returned Promise is awaited). It rejects with the error your function threw (an `Error` arrives as an `Error`, with its message), when the worker script itself fails to load, when a `localDependencies` entry has no name, when the call takes longer than `timeout`, when `terminateWorkerFn()` is called meanwhile and when another call is still running: one call at a time, await it before the next one.

`workerFnStatus` follows the last call: `idle` before the first one (and after a termination), then `running`, `success`, `error` or `timeout`.

The worker gets created at the first call and is kept for the next ones, so repeated calls do not pay the startup cost again. `terminateWorkerFn()` kills it (rejecting a running call); the next call starts a fresh one. A `timeout` kills it too: the call that exceeded it is still running inside the worker and there is no other way to stop it. The composable terminates the worker by itself when the component gets destroyed.

The hooks report the outcome of each call, with the arguments it was made with, so that one handler can react wherever the call came from: `onSuccess(result, args)` when it resolves, `onError(error, args)` when it rejects with an error (your function threw, the worker script failed to load or could not be built from an unnamed `localDependencies` entry, an argument could not be cloned) and `onTimeout(args)` when it exceeds `timeout`. `onTerminate(reason)` gets called right after the worker got killed, after the outcome hook of the call it interrupted, with `reason` naming the cause: `'terminate'` for a `terminateWorkerFn()` call, `'timeout'`, `'error'` for a failing script or `'unmount'` for the component being destroyed. A rejection caused by `terminateWorkerFn()` or by a call made while another one runs is not an outcome of your function, so no hook reports it.

## The function is serialized

Your function travels to the worker as source code (through `Function.prototype.toString()`), where it gets evaluated in the worker's own global scope. This has consequences:

- it must be self-contained: no variables from the surrounding scope, no imported modules, no component state, no `$q`. Only its arguments, the `dependencies` and the `localDependencies` listed in the options and what a worker offers by itself (`fetch()`, `self`, `crypto`, `indexedDB`...) are available to it
- its arguments and its return value must be [structured-cloneable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm): plain data, Arrays, typed arrays, `Map`, `Set`, `Date`, `Blob`, `File`, `ImageData`... but no functions, DOM nodes, class instances (they arrive as plain objects) or Vue reactive proxies (unwrap them with `toRaw()` first)
- a result or thrown value that cannot be cloned rejects with the clone error's message (a String) instead
- the worker is a classic one, so the function cannot use `import` syntax (static or dynamic); load what it needs through `dependencies`

`localDependencies` inlines your own helper functions (or classes) into the worker script; they must have a name (a `function` declaration or an arrow function assigned to a `const`), and your function calls them by that name. `dependencies` lists scripts to load through `importScripts()` before your function runs; they must be classic scripts (no ES modules), and what they define on the global scope is then available.

```js
import { useWebWorkerFn } from 'quasar'

function distance (a, b) {
  return Math.hypot(a.x - b.x, a.y - b.y)
}

setup () {
  const { runWorkerFn } = useWebWorkerFn(
    (points, origin) => points.filter(p => distance(p, origin) < 10),
    { localDependencies: [ distance ] }
  )

  // ...
}
```

## Transferring instead of copying

Arguments get copied to the worker. For a large `ArrayBuffer` (the pixels of an image, a file's content) you can move it instead, which costs nothing regardless of its size: the `transfer` option receives the call's arguments and returns the objects to transfer. Once moved, the object is unusable on the main thread (its `byteLength` becomes 0).

```js
const { runWorkerFn } = useWebWorkerFn(
  (pixels, width, height) => {
    const view = new Uint8ClampedArray(pixels)
    // ...heavy work on view...
    return result
  },
  { transfer: pixels => [pixels] }
)

await runWorkerFn(imageData.data.buffer, imageData.width, imageData.height)
```

The return value always gets copied back.

## Examples

Example "Basic":

```vue
<template>
  <div class="row items-center q-gutter-sm q-mb-md">
    <q-btn
      color="primary"
      label="Sort 2 million numbers in a worker"
      no-caps
      :loading="workerFnStatus === 'running'"
      @click="sortInWorker"
    />
    <q-btn
      color="grey-8"
      label="Sort on the main thread"
      no-caps
      outline
      @click="sortInline"
    />
  </div>

  <div class="row items-center q-gutter-sm q-mb-md">
    <q-badge :label="workerFnStatus" color="secondary" />
    <div v-if="result !== null">
      {{ result.where }}: median {{ result.median }} in {{ result.ms }}ms
    </div>
  </div>

  <q-spinner-gears size="40px" color="primary" />
  <div class="text-caption">
    The spinner keeps turning while the worker sorts, and freezes when the
    main thread does it.
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useWebWorkerFn } from 'quasar'

function medianOfRandom(size) {
  const numbers = new Float64Array(size)
  for (let i = 0; i < size; i++) {
    numbers[i] = Math.random()
  }
  numbers.sort()
  return numbers[size >> 1].toFixed(4)
}

const { workerFnStatus, runWorkerFn } = useWebWorkerFn(medianOfRandom)

const size = 2_000_000
const result = ref(null)

async function sortInWorker() {
  const start = performance.now()
  const median = await runWorkerFn(size)
  result.value = {
    where: 'Worker',
    median,
    ms: Math.round(performance.now() - start)
  }
}

function sortInline() {
  const start = performance.now()
  const median = medianOfRandom(size)
  result.value = {
    where: 'Main thread',
    median,
    ms: Math.round(performance.now() - start)
  }
}
</script>
```

Example "Timeout and termination":

```vue
<template>
  <div class="row items-center q-gutter-sm q-mb-md">
    <q-btn
      color="primary"
      label="Run for 1s (timeout: 3s)"
      no-caps
      :disable="workerFnStatus === 'running'"
      @click="run(1000)"
    />
    <q-btn
      color="orange"
      label="Run for 10s (times out)"
      no-caps
      :disable="workerFnStatus === 'running'"
      @click="run(10000)"
    />
    <q-btn
      v-if="workerFnStatus !== 'idle' && workerFnStatus !== 'timeout'"
      color="negative"
      label="terminateWorkerFn()"
      no-caps
      @click="terminateWorkerFn"
    />
  </div>

  <div class="row items-center q-gutter-sm">
    <q-badge :label="workerFnStatus" color="secondary" />
    <div>{{ message }}</div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useWebWorkerFn } from 'quasar'

const { workerFnStatus, runWorkerFn, terminateWorkerFn } = useWebWorkerFn(
  async ms => {
    await new Promise(resolve => {
      setTimeout(resolve, ms)
    })
    return `${ms}ms of work done`
  },
  { timeout: 3000 }
)

const message = ref('')

async function run(ms) {
  message.value = 'running...'
  try {
    message.value = await runWorkerFn(ms)
  } catch (err) {
    message.value = err.message
  }
}
</script>
```

> [!TIP]
> **Content Security Policy**
>
> The worker script is created from a Blob URL, so the `worker-src` directive of your CSP (which falls back to `script-src`) must allow `blob:`. Without it, `runWorkerFn()` rejects with a SecurityError. This concerns apps that ship a strict CSP, such as Electron apps with a `Content-Security-Policy` meta tag.
