Skip to content

Продвинутые возможности

Передача владения (Transferables)

В воркер — через RunOptions.transfer:

ts
const buffer = new ArrayBuffer(1024 * 1024)
const result = await run(buffer, { transfer: [buffer] })
// buffer.byteLength === 0 сразу же — он был отсоединён, а не скопирован

Обратно из воркера — через ctx.transfer(...), зеркально предыдущему, для обработчика, который хочет вернуть большой буфер (например, изменённое изображение, кадр, отрисованный на OffscreenCanvas) без копирования:

ts
// resize.worker.ts
export default defineWorkerHandler((input: ResizeInput, ctx) => {
  const output = resize(input) // создаёт новый ArrayBuffer
  ctx.transfer(output) // отправляется обратно без копирования вместо структурного клонирования
  return output
})

ctx.transfer() не требует, чтобы переданный объект был частью возвращаемого значения — вызывайте его с любыми объектами, которые должны отправиться вместе с результатом. Можно вызывать более одного раза — включаются объекты, переданные во всех вызовах.

Потоковая передача результатов (Streaming / Chunked Results)

Для больших наборов данных, когда нужны промежуточные результаты без ожидания полного завершения:

ts
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])
})

Сторона воркера:

ts
// 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) — отправляет частичный результат на основной поток (без ограничения частоты)
  • Чанки накапливаются по порядку; финальный результат отделён от чанков
  • Полезно для прогрессивного рендеринга, обновлений в реальном времени или экономной по памяти обработки

Отмена

ts
const controller = new AbortController()
const promise = run(input, { signal: controller.signal })
controller.abort() // промис отклоняется с AbortError немедленно — независимо от того, что делает воркер

Если вы не передали свой signal, run() создаёт его внутри сам; cancel() отменяет его. retries никогда не применяется к отменённому запуску.

Стратегия повторов с backoff

Для временных сбоёв настройте автоматические повторы с экспоненциальным backoff:

ts
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 или нестабильных операций
ts
// Продвинутый вариант: экспоненциальный backoff со случайным джиттером
{
  retries: 5,
  retryDelay: (attempt) => {
    const baseDelay = 1000 * 2 ** attempt
    const jitter = Math.random() * 1000
    return Math.min(baseDelay + jitter, 30000)
  },
}

Мемоизация / кэш результатов

Для чистых функций воркера (один и тот же вход → один и тот же выход) включите LRU-кэширование:

ts
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)

Чтобы избежать задержки холодного старта на первой задаче, можно заранее создать воркеры без выполнения какой-либо работы:

ts
// Один воркер
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 или после первоначальной загрузки страницы), чтобы взаимодействие оставалось быстрым.