Skip to content

WorkerClient

Anfrage und Antwort über einen Web Worker. Ein blanker Worker kennt nur „eine Nachricht senden“ und „eine Nachricht empfangen“: Schickst du zwei Aufgaben gleichzeitig los, kommen zwei Nachrichten zurück, und nichts sagt dir, welche zu welcher gehört. WorkerClient stempelt jeder Anfrage eine Kennung auf und leitet jede Antwort an das Promise zurück, das dazugehört.

API

new WorkerClient(options)

ParameterBeschreibungTypStandard
createWie der Worker erzeugt wird() => WorkerErforderlich
isProgressIst das eine Fortschrittsnachricht? (beendet die Anfrage nicht)(res) => booleanres.type === 'progress'
getProgressHolt den Inhalt des Fortschritts heraus(res) => Progressres.progress
isErrorIst das eine Fehlernachricht?(res) => booleanres.type === 'error'
getErrorMessageDer Fehlertext(res) => stringres.message
timeoutFrist je Anfrage (ms); lehnt nur diese eine abnumberkeine
ElementBeschreibung
send(request, onProgress?, transfer?)Schickt eine Anfrage und wartet auf ihre Antwort
dispose()Beendet den Worker und lehnt alles Laufende ab
activeOb der Worker bereits erzeugt wurde
pendingCountWie viele Anfragen gerade laufen

serveWorker(handler, options?) — die Worker-Seite

Das Gegenstück, das im Worker läuft. Es liest die operationId jeder Anfrage aus, wartet auf deinen Handler und schickt die Antwort mit eben dieser Kennung zurück.

ParameterBeschreibungTyp
handler(request, { progress }) => Response | Promise<Response>Function
options.scopeWo gelauscht wird. Standardmäßig self; für einen Port oder einen Test überschreibenobject
options.resultTypeDer type der Antwort, wenn der Handler etwas zurückgibt, das kein Objekt ist. Standard 'result'string

Gibt eine stop-Funktion zurück, die den Listener wieder entfernt.

Beispiel

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();

Und die Worker-Seite:

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) };
});

Hinweise

  1. Der Worker entsteht erst, wenn er gebraucht wird, beim ersten send: Schwere Arbeit soll nicht schon beim Laden der Seite anlaufen.
  2. Fortschrittsnachrichten beenden die Anfrage nicht, eine einzelne Anfrage kann also viele Zwischenstände senden und sich am Ende trotzdem genau einmal erfüllen.
  3. Stürzt der Worker ab, werden alle laufenden Anfragen abgelehnt. Ein nicht abgefangener Fehler im Worker trägt keine operationId, lässt sich also keiner einzelnen Anfrage zuordnen.
  4. dispose() beendet und lehnt ab; das nächste send baut den Worker neu auf.
  5. Eine abgelaufene Frist lehnt nur diese eine Anfrage ab und lässt den Worker am Leben.
  6. Nimm für große Puffer transfer, um den Besitz zu übergeben, statt eine Kopie strukturiert zu klonen.
  7. serveWorker fängt auch synchron geworfene Fehler ab. Ein synchroner Wurf innerhalb von onmessage entwischt zum Fehler-Handler des Workers, und auf diesem Weg reist keine operationId mit — der Client könnte dann nur alle laufenden Anfragen scheitern lassen statt der einen, die tatsächlich kaputtging.
  8. Dass beide Hälften zusammen ausgeliefert werden, hat einen Grund. Die Worker-Seite selbst zu schreiben ist genau die Stelle, an der das Zurückspiegeln der Kennung und die Hülle für Fehler von Projekt zu Projekt auseinanderdriften.

Veröffentlicht unter der MIT-Lizenz.