Skip to content

WorkerClient

Requisição e resposta sobre um Web Worker. Um worker puro só sabe «enviar uma mensagem» e «receber uma mensagem»: mande duas tarefas ao mesmo tempo e voltam duas mensagens sem como saber qual é de qual. O WorkerClient carimba um identificador em cada requisição e encaminha cada resposta para a promessa que lhe cabe.

API

new WorkerClient(options)

ParâmetroDescriçãoTipoPadrão
createComo o worker é construído() => WorkerObrigatório
isProgressÉ uma mensagem de progresso? (não resolve a requisição)(res) => booleanres.type === 'progress'
getProgressTira o conteúdo do progresso(res) => Progressres.progress
isErrorÉ uma mensagem de erro?(res) => booleanres.type === 'error'
getErrorMessageO texto do erro(res) => stringres.message
timeoutPrazo por requisição (ms); rejeita apenas aquelanumbernenhum
MembroDescrição
send(request, onProgress?, transfer?)Envia uma requisição e espera a resposta
dispose()Encerra o worker e rejeita tudo que estiver em andamento
activeSe o worker já foi criado
pendingCountQuantas requisições estão em andamento

serveWorker(handler, options?) — o lado do worker

A contraparte que roda dentro do worker. Lê o operationId de cada requisição, espera o seu manipulador e devolve a resposta carregando esse mesmo identificador.

ParâmetroDescriçãoTipo
handler(request, { progress }) => Response | Promise<Response>Function
options.scopeOnde escutar. Por padrão self; troque para uma porta ou um testeobject
options.resultTypeO type da resposta quando o manipulador devolve algo que não é objeto. Padrão 'result'string

Devolve uma função stop que tira o ouvinte.

Exemplo

js
import { WorkerClient } from 'ranuts';

const client = new WorkerClient({
  create: () => new Worker(new URL('./nlp.worker.ts', import.meta.url), { type: 'module' }),
});

await client.send({ type: 'load', modelId }, (p) => renderProgress(p.progress));
const { scores } = await client.send({ type: 'classify', lines });
client.dispose();

E o lado do worker:

js
// nlp.worker.ts
import { serveWorker } from 'ranuts';

serveWorker(async (request, { progress }) => {
  if (request.type === 'load') {
    const device = await loadModel(request.modelId, (p) => progress(p));
    return { type: 'loaded', device };
  }
  return { type: 'result', scores: await classify(request.lines) };
});

Notas

  1. O worker é criado preguiçosamente, no primeiro send: trabalho pesado não deve começar no carregamento da página.
  2. Mensagens de progresso não resolvem a requisição, de modo que uma única requisição pode emitir várias atualizações e ainda assim resolver uma vez no fim.
  3. Se o worker quebra, todas as requisições em andamento são rejeitadas. Um erro não capturado dentro de um worker não traz operationId, então não dá para atribuí-lo a uma requisição.
  4. dispose() encerra e rejeita; o send seguinte reconstrói o worker.
  5. Um prazo vencido rejeita só aquela requisição e deixa o worker vivo.
  6. Use transfer para buffers grandes, para passar a posse em vez de clonar uma cópia.
  7. O serveWorker também captura exceções síncronas. Uma exceção síncrona dentro do onmessage escapa para o manipulador de erros do worker, e por esse caminho não viaja nenhum operationId; o cliente então só poderia derrubar todas as requisições em andamento, e não a que de fato quebrou.
  8. As duas metades vêm juntas de propósito. Escrever à mão o lado do worker é justamente onde o eco do identificador e o envelope do erro começam a divergir de projeto para projeto.

Publicado sob a licença MIT.