perspective-dev/perspective · error · Error

Init error

Error message

Init error

What it means

bootstrapWorker validates that the InitMsg carries every resource needed to start the wasm-backed renderer: clientWorkerURL, clientWasm, and proxyPort. If any of the three is missing/null/empty it throws a generic 'Init error'. This is a fail-fast guard so the worker never proceeds with a half-initialized wasm module.

Solutions

  1. Inspect the InitMsg at the sender: log clientWorkerURL, clientWasm, and proxyPort before postMessage and fix whichever is missing.
  2. Ensure all init fields are resolved (URLs fetched, wasm bytes available, MessagePort acquired) BEFORE constructing/posting the init message.
  3. Check build/asset pipeline that the wasm and client worker assets are emitted and reachable, not just referenced.
  4. Verify the MessagePort is transferred in the postMessage transfer list and not closed beforehand.
  5. Wrap worker bootstrap in a retry/await so a slow asset load completes before init.

Example fix

// before: init sent before wasm resolved
worker.postMessage({ type: "init", clientWorkerURL: wasmUrl, clientWasm: undefined, proxyPort: port });
// after: await all resources first
const wasm = await loadClientWasm();
worker.postMessage({ type: "init", clientWorkerURL: wasmUrl, clientWasm: wasm, proxyPort: port }, [port]);
Defensive patterns

Strategy: validation

Validate before calling

function canInitWorker(msg: InitMsg): boolean {
  return Boolean(msg.clientWorkerURL) && Boolean(msg.clientWasm) && Boolean(msg.proxyPort);
}
// call before postMessage
if (!canInitWorker(init)) throw new Error("Refusing to init worker: missing clientWorkerURL/clientWasm/proxyPort");

Type guard

function isInitMsgReady(msg: Partial<InitMsg>): msg is InitMsg {
  return msg.clientWorkerURL != null && msg.clientWasm != null && msg.proxyPort != null;
}

Try / catch

try {
  const renderer = await bootstrapWorker(initMsg, port);
} catch (e) {
  if (e instanceof Error && e.message === "Init error") {
    console.error("Worker init message incomplete — check clientWorkerURL, clientWasm, proxyPort", initMsg);
    await retryInitAfterAssetsLoaded();
  } else throw e;
}

Prevention

When it happens

Trigger: Posting an InitMsg to the worker where any of msg.clientWorkerURL, msg.clientWasm, or msg.proxyPort is undefined, null, or an empty string — e.g. the host constructed the init message before the wasm finished loading, or forgot to transfer the MessagePort.

Common situations: Race between asset loading (wasm URL fetch) and worker boot; config object partially populated because a build/CDN step dropped the wasm asset; caller forgot to pass the proxy MessagePort in the init message; async init code that resolves the init promise before all fields are set.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of perspective-dev/perspective@11c8238c0c (2026-09-09). Data as JSON: /api/errors/8402231acc4e2b16. Report an issue: GitHub.

Appendix: source

Thrown at packages/viewer-charts/src/ts/worker/renderer.worker.ts:865

}

/**
 * Detect whether this module is loaded in a Web Worker scope.
 */
const IS_WORKER_SCOPE = typeof (globalThis as any).importScripts === "function";

/**
 * Worker-mode bootstrap: receives the host's `InitMsg`, instantiates
 * wasm, registers fonts, opens a `Client` against the host's
 * `ProxySession`, and constructs a {@link WorkerRenderer} bound to the
 * supplied control port (which in worker scope is `self`).
 */
async function bootstrapWorker(
    msg: InitMsg,
    host: MessagePort,
): Promise<WorkerRenderer> {
    if (!msg.clientWorkerURL || !msg.clientWasm || !msg.proxyPort) {
        throw new Error("Init error");
    }

    const module = (await import(
        msg.clientWorkerURL.toString()
    )) as typeof wasm_module_type;

    await module.initSync({ module: msg.clientWasm });

    // Register every `@font-face` the host found in its document so
    // Canvas2D `ctx.font` lookups inside this worker resolve correctly.
    if (msg.fontFaces?.length) {
        await Promise.all(msg.fontFaces.map(loadFontDeduped));
    }

    const proxyPort = msg.proxyPort;
    const client = new module.Client(
        async (proto: Uint8Array) => {
            const buf = proto.slice().buffer;

View on GitHub (pinned to 11c8238c0c)