Справочник API
defineWorkerHandler()
Сторона воркера. Автоматически подключает протокол сообщений run/cancel — вам нужно написать только саму функцию-обработчик.
import { defineWorkerHandler, type WorkerContext } from 'vue-worker-kit/worker'
export default defineWorkerHandler(async (input: In, ctx: WorkerContext): Promise<Out> => {
// ...
})ctx: WorkerContext:
| Поле | Тип | Описание |
|---|---|---|
signal | AbortSignal | Срабатывает, когда задача отменена с основного потока — проверка опциональна, отмена кооперативная |
reportProgress(value) | (0..1) => void | Отправляет прогресс на основной поток, ограничено ~20 сообщениями/сек |
transfer(...transferables) | (...Transferable[]) => void | Помечает объекты для отправки обратно с результатом без копирования (zero-copy) вместо структурного клонирования — см. Передача владения |
reportChunk(chunk) | (chunk: unknown) => void | Отправляет промежуточный результат без ограничения частоты — см. Потоковая передача результатов |
defineWorkerHandler запускает цикл обработки сообщений только тогда, когда действительно выполняется в глобальной области выделенного или общего воркера (проверяется через self instanceof DedicatedWorkerGlobalScope/SharedWorkerGlobalScope). Импорт этого файла где-либо ещё — например, случайно из основного бандла — ничего не делает. Один и тот же файл работает и с new Worker(...) (через useWorker()/createWorkerPool()), и с new SharedWorker(...) (через useSharedWorker()) — см. useSharedWorker().
Вызывать reportProgress(1) самостоятельно перед возвратом не нужно — финальное, не ограниченное по частоте обновление прогресса до 1 всегда отправляется прямо перед результатом, независимо от того, каким был последний ограниченный вызов. Без этого обработчик, который сообщает прогресс только в контрольных точках (например, каждые 5%), мог бы навсегда оставить progress основного потока застрявшим ниже 1, поскольку контрольная точка, ближайшая к концу, может попасть в окно ограничения предыдущего вызова и незаметно потеряться.
useWorker()
Composable основного потока, оборачивает один лениво создаваемый воркер.
const { run, isRunning, progress, error, cancel, warmup } = useWorker<typeof import('./x.worker')>(
() => new Worker(new URL('./x.worker.ts', import.meta.url), { type: 'module' }),
{ idleTimeout: 30_000, retries: 0 },
)
// Опционально: заранее создать воркер, не выполняя задачу (избегает задержки холодного старта при первом запуске)
await warmup()
const output = await run(input, { transfer: [input.buffer], signal: controller.signal })Опции:
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
idleTimeout | number | false | 30000 | Воркер самозавершается после стольки мс простоя (освобождает память); следующий run() прозрачно пересоздаёт его |
retries | number | 0 | Автоматические повторы при отклонении — никогда не применяются к отменам (AbortError всегда отклоняется немедленно) |
retryDelay | (attempt: number) => number | — | Задержка перед каждым повтором — см. Стратегия повторов с backoff |
hardCancelOnAbort | boolean | false | При abort() немедленно завершить и пересоздать воркер вместо ожидания кооперативной обработки через ctx.signal |
cache | { cache: 'lru', maxCacheSize?: number } | — | Мемоизирует результаты по входным данным — см. Мемоизация / кэш результатов |
streaming | boolean | false | Включает ctx.reportChunk()/chunks — см. Потоковая передача результатов |
Возвращает:
run(input, options?) => Promise<Output>—options: { transfer?: Transferable[], signal?: AbortSignal }isRunning: ComputedRef<boolean>,progress: ShallowRef<number>,error: ShallowRef<WorkerError | null>cancel()— отменяет текущий вызов(ы)run(), не получившие собственныйsignalwarmup(): Promise<void>— заранее создаёт воркер без выполнения задачи (полезно, чтобы избежать задержки холодного старта)chunks?: ShallowRef<unknown[]>— присутствует только приstreaming: true(см. Потоковая передача результатов)- автоматический
terminate()приonScopeDispose, если вызвано внутри активной effect-области
Вход run() пропускается через toRaw() перед отправкой — значение ref/reactive, прочитанное прямо из компонента (() => list.value), не клонируется структурно как живой Proxy, поэтому отправляется именно сырой снапшот.
createWorkerPool() / useWorkerPool()
Для множества мелких независимых задач (изменение размера сотен изображений и т. п.) — vue-worker-kit/pool.
import { createWorkerPool } from 'vue-worker-kit/pool'
const pool = createWorkerPool<typeof import('./resize.worker')>(
() => new Worker(new URL('./resize.worker.ts', import.meta.url), { type: 'module' }),
)
// Заранее создать все воркеры до размера пула (опционально, избегает задержки холодного старта на первых задачах)
await pool.warmup()
// Обработка массива с передачей по элементу и глобальным сигналом отмены
const thumbnails = await pool.map(files, {
concurrency: 4,
transfer: (file) => [file.buffer], // zero-copy для каждого элемента
signal: abortController.signal, // отменить все выполняющиеся задачи
})
const one = await pool.run(files[0])pool.run(input, options?)— ставит задачу в очередь первому свободному воркеру,options: { transfer?: Transferable[], signal?: AbortSignal }pool.map(items, options?)— обрабатывает массив с ограниченным параллелизмом, результаты в порядке входных данных. Опции:concurrency?: number— максимум параллельных задач (по умолчанию:pool.size)transfer?: (item: T) => Transferable[]— функция передачи zero-copy для каждого элементаsignal?: AbortSignal— глобальный сигнал отмены для всех элементов
pool.stats: ComputedRef<{ busy: number; idle: number; queued: number }>— реактивно, используется панелью devtoolspool.terminate()— завершает весь пулpool.warmup(): Promise<void>— заранее создаёт все воркеры доsize, не выполняя задачи- воркеры создаются лениво, до
size, по мере поступления задач — не все сразу size(опция) по умолчанию равенnavigator.hardwareConcurrency— собственному числу логических ядер/потоков браузера на машине, где реально работает ваше приложение, а не числу, выбранному на этапе разработки. Передайтеsizeявно, чтобы переопределить его (например, ограничить, или еслиnavigatorсообщает то, чему вы не доверяете — некоторые браузеры с усиленной приватностью ограничивают или округляют это значение). Откатывается к4там, гдеnavigatorне существует (SSR).useWorkerPool()— тот же API с автоматическим завершением черезonScopeDisposeдля использования прямо вsetup()
useWorkerComputed()
vue-worker-kit/computed. computed(), который пересчитывается внутри воркера при каждом изменении своего реактивного источника, с автоматическим отбрасыванием устаревших запусков.
import { useWorkerComputed } from 'vue-worker-kit/computed'
const sorted = useWorkerComputed<typeof import('./heavy-sort.worker')>(
() => new Worker(new URL('./heavy-sort.worker.ts', import.meta.url), { type: 'module' }),
() => list.value, // отслеживается как источник watchEffect
{ debounce: 150 },
)
// sorted.value — undefined до первого результата, затем последний АКТУАЛЬНЫЙ результат
// sorted.isRunning, sorted.errorОбработка гонок: каждый запуск получает внутренний номер поколения. Если источник меняется снова до прихода результата запуска, этот результат просто отбрасывается по прибытии (никогда не откатывает sorted.value к устаревшему значению), а ctx.signal вытесненного запуска отменяется (кооперативно — обработчик сам решает, проверять его или нет). debounce (мс) предотвращает запуск воркера на каждый реактивный тик (например, на каждое нажатие клавиши).
useSharedWorker()
vue-worker-kit/shared — переиспользует один SharedWorker для всех вкладок/окон одного источника, которые к нему подключаются, вместо одного воркера на вкладку.
import { useSharedWorker } from 'vue-worker-kit/shared'
const { run, connect, disconnect, portCount } = useSharedWorker<typeof import('./shared.worker')>(
() => new SharedWorker(new URL('./shared.worker.ts', import.meta.url), { type: 'module' }),
)
// Опционально — run() подключается лениво сам; вызовите это, чтобы подключиться заранее.
connect()
const result = await run(data)
// Закрывает порт этой вкладки. НЕ завершает воркер — остальные вкладки остаются подключены.
disconnect()connect(): void— устанавливает соединение этой вкладки (идемпотентно;run()тоже вызывает его лениво, если вы пропустили этот шаг)disconnect(): void— закрывает только порт этой вкладки; общий воркер продолжает работать для всех остальных подключённых вкладок. Вызывается автоматически приonScopeDispose, если используется внутриsetup().portCount: Ref<number>— количество вкладок, чьё подключение видел воркер, по последней рассылке от самого воркера. Best-effort: уMessagePortнет уведомления уровня платформы о том, что «другая сторона пропала», поэтому значение уменьшается только по кооперативному вызовуdisconnect()— упавшая или принудительно закрытая вкладка никогда не вычитается.run(input, options?),isRunning,progress,error,cancel()— та же семантика, что и уuseWorker()- Опции:
retries,retryDelay,cache,streaming— как уuseWorker(). НетidleTimeout/hardCancelOnAbort: время жизни общего воркера не принадлежит какой-то одной вкладке, поэтомуconnect()/disconnect()— это весь жизненный цикл целиком, а не таймер простоя. - Поддержка браузеров: Chrome, Firefox, Edge Desktop. ❌ Не поддерживается в Safari iOS или Chrome Android — там конструктор
SharedWorkerвообще не существует.connect()/run()в этом случае бросаютWorkerUnavailableError, так же какuseWorker()при SSR.
Файл на стороне воркера — обычный модуль defineWorkerHandler(): тот же самый файл работает и с new Worker(...) (через useWorker()), и с new SharedWorker(...) (через useSharedWorker()); ему не нужно знать, под каким из них он выполняется.
// shared.worker.ts
import { defineWorkerHandler } from 'vue-worker-kit/worker'
export default defineWorkerHandler(async (data: In, ctx) => {
return processData(data)
})Devtools
vue-worker-kit/devtools — самостоятельная отладочная панель, без зависимости от @vue/devtools-api (сохраняет пакет свободным от зависимостей).
import { createWorkerActivityMonitor, WorkerActivityPanel } from 'vue-worker-kit/devtools'
const monitor = createWorkerActivityMonitor(pool) // или один экземпляр useWorker()/useSharedWorker()<WorkerActivityPanel :monitor="monitor" />Показывает количество занятых/свободных воркеров, длину очереди, среднее время задачи и последние N ошибок — реактивно, на основе внутренней подписки (без опроса).