Продвинутые возможности
Передача владения (Transferables)
В воркер — через RunOptions.transfer:
const buffer = new ArrayBuffer(1024 * 1024)
const result = await run(buffer, { transfer: [buffer] })
// buffer.byteLength === 0 сразу же — он был отсоединён, а не скопированОбратно из воркера — через ctx.transfer(...), зеркально предыдущему, для обработчика, который хочет вернуть большой буфер (например, изменённое изображение, кадр, отрисованный на OffscreenCanvas) без копирования:
// resize.worker.ts
export default defineWorkerHandler((input: ResizeInput, ctx) => {
const output = resize(input) // создаёт новый ArrayBuffer
ctx.transfer(output) // отправляется обратно без копирования вместо структурного клонирования
return output
})ctx.transfer() не требует, чтобы переданный объект был частью возвращаемого значения — вызывайте его с любыми объектами, которые должны отправиться вместе с результатом. Можно вызывать более одного раза — включаются объекты, переданные во всех вызовах.
Потоковая передача результатов (Streaming / Chunked Results)
Для больших наборов данных, когда нужны промежуточные результаты без ожидания полного завершения:
import { useWorker } from 'vue-worker-kit'
const { run, chunks, isRunning } = useWorker<typeof import('./process.worker')>(
() => new Worker(new URL('./process.worker.ts', import.meta.url), { type: 'module' }),
{ streaming: true }, // обязательно — без этого `chunks` будет `undefined`, а не ref
)
// Обработка большого набора данных с потоковыми результатами
const finalResult = await run(largeDataset)
// chunks.value содержит все промежуточные результаты по мере их поступления
watch(chunks, (newChunks) => {
console.log('Received chunk:', newChunks[newChunks.length - 1])
})Сторона воркера:
// process.worker.ts
import { defineWorkerHandler } from 'vue-worker-kit/worker'
export default defineWorkerHandler(async (items: LargeDataset[], ctx) => {
const results: Result[] = []
for (let i = 0; i < items.length; i += 100) {
const batch = items.slice(i, i + 100)
const processed = await processBatch(batch)
// Отправить промежуточный результат немедленно
ctx.reportChunk(processed)
results.push(...processed)
if (ctx.signal.aborted) throw ctx.signal.reason
}
return results // Финальный результат
})chunks: ShallowRef<unknown[]>— реактивный массив всех полученных чанковctx.reportChunk(data)— отправляет частичный результат на основной поток (без ограничения частоты)- Чанки накапливаются по порядку; финальный результат отделён от чанков
- Полезно для прогрессивного рендеринга, обновлений в реальном времени или экономной по памяти обработки
Отмена
const controller = new AbortController()
const promise = run(input, { signal: controller.signal })
controller.abort() // промис отклоняется с AbortError немедленно — независимо от того, что делает воркерЕсли вы не передали свой signal, run() создаёт его внутри сам; cancel() отменяет его. retries никогда не применяется к отменённому запуску.
Стратегия повторов с backoff
Для временных сбоёв настройте автоматические повторы с экспоненциальным backoff:
import { useWorker } from 'vue-worker-kit'
const { run, error } = useWorker<typeof import('./api.worker')>(
() => new Worker(new URL('./api.worker.ts', import.meta.url), { type: 'module' }),
{
retries: 3,
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 10000), // 1с, 2с, 4с, ограничено 10с
},
)
// При провале автоматически повторяет с увеличивающейся задержкой
const result = await run(data)retries: number— максимум попыток повтора (по умолчанию:0, без повторов)retryDelay: number | ((attempt: number) => number)— задержка между повторами- Если
number: постоянная задержка в мс - Если функция: динамическая задержка в зависимости от номера попытки (нумерация с 1)
- Если
- Повторы применяются только к ошибкам, не связанным с отменой (
AbortErrorотклоняется немедленно) - Распространённый паттерн: экспоненциальный backoff с джиттером для вызовов API или нестабильных операций
// Продвинутый вариант: экспоненциальный backoff со случайным джиттером
{
retries: 5,
retryDelay: (attempt) => {
const baseDelay = 1000 * 2 ** attempt
const jitter = Math.random() * 1000
return Math.min(baseDelay + jitter, 30000)
},
}Мемоизация / кэш результатов
Для чистых функций воркера (один и тот же вход → один и тот же выход) включите LRU-кэширование:
import { useWorker } from 'vue-worker-kit'
const { run } = useWorker<typeof import('./hash.worker')>(
() => new Worker(new URL('./hash.worker.ts', import.meta.url), { type: 'module' }),
{
cache: { cache: 'lru', maxCacheSize: 100 }, // хранить последние 100 результатов
},
)
// Первый вызов — выполняется в воркере
const hash1 = await run(data)
// Второй вызов с тем же входом (сравнение через JSON.stringify) — возвращает
// закэшированный результат мгновенно, без вызова воркера, без round-trip postMessage
const hash2 = await run(data) // hash1 === hash2Опции (cache: UseWorkerCacheOptions):
cache: 'lru'— включить LRU-кэш (не задано/опущено — выключает его)maxCacheSize: number— максимум записей перед вытеснением самой старой (по умолчанию:50)
Ключ кэша — JSON.stringify(input) (экспортируется как createCacheKey(), если хотите сами анализировать коллизии) — входы, которые сериализуются одинаково (включая порядок ключей объекта), делят одну запись кэша.
У useWorkerComputed() нет опции cache — её собственный механизм номеров поколений уже отбрасывает устаревшие/вытесненные результаты, а её source() обычно выдаёт новый вход на каждом реактивном тике в любом случае, так что мемоизация по ключу мало на чём сработала бы.
Обработка ошибок
- Ошибка, выброшенная внутри обработчика, сериализуется как
{ name, message, stack }и восстанавливается на основном потоке какWorkerError..workerStack— исходный стек внутри воркера;.cause— синтетическая ошибка, созданная в точке вызоваrun()(до пересечения границы воркера) — так что обе стороны сбоя видны вместе в консоли/Sentry. - Сбой на уровне протокола (например, объект, который не клонируется структурно) становится
WorkerErrorсname: 'DataCloneError', а не необработанным исключением. WorkerUnavailableErrorвыбрасывается вместо сырогоReferenceError: Worker is not defined, когдаrun()вызывается там, где нет глобальногоWorker(обычно SSR) — она никогда не оборачивается и не повторяется.
Жизненный цикл воркера
- Таймаут простоя — воркер, простаивающий дольше
idleTimeout, завершается; следующийrun()прозрачно поднимает новый (небольшая задержка на первом вызове после простоя — ожидаемо). - Автозавершение по области видимости —
useWorker/useWorkerPool, вызванные внутриsetup(), завершают свои воркеры приonScopeDispose, избегая классической утечки при навигации в SPA. - Воркеры пула ленивы — создаются по мере поступления задач, до
size, а не все сразу в момент вызоваcreateWorkerPool().
Прогрев (Warmup)
Чтобы избежать задержки холодного старта на первой задаче, можно заранее создать воркеры без выполнения какой-либо работы:
// Один воркер
const { warmup, run } = useWorker<typeof import('./x.worker')>(
() => new Worker(new URL('./x.worker.ts', import.meta.url), { type: 'module' }),
)
await warmup() // Воркер теперь создан и готов
const result = await run(data) // Без задержки на создание воркера
// Пул — заранее создать все воркеры до размера пула
const pool = createWorkerPool<typeof import('./resize.worker')>(
() => new Worker(new URL('./resize.worker.ts', import.meta.url), { type: 'module' }),
{ size: 4 },
)
await pool.warmup() // Все 4 воркера теперь созданы
const results = await pool.map(items) // Немедленное выполнение, без холодных стартовПрогрев полезен, когда вы знаете, что вот-вот начнётся ресурсоёмкая для воркера операция (например, пользователь нажал кнопку «Обработать»), и вы хотите убрать задержку создания воркера в ~50-200 мс. Вызывайте его во время простоя (например, в onMounted или после первоначальной загрузки страницы), чтобы взаимодействие оставалось быстрым.