{"record":{"id":"2a2a993ed189ff89","repo":"d2lang/d2","slug":"d2-instance-has-been-disposed","errorCode":null,"errorMessage":"D2 instance has been disposed","messagePattern":"D2 instance has been disposed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"d2js/js/src/index.js","lineNumber":97,"sourceCode":"    const wasmExecContent = isNode ? await loadFile(\"./wasm_exec.js\") : null;\n    const wasmBinary = await loadFile(\"./d2.wasm\");\n\n    const messageHandler = this.setupMessageHandler();\n\n    this.worker.postMessage({\n      type: \"init\",\n      data: {\n        wasm: wasmBinary,\n        wasmExecContent: isNode ? wasmExecContent.toString() : null,\n      },\n    });\n\n    return messageHandler;\n  }\n\n  async sendMessage(type, data) {\n    if (this.disposed) {\n      throw new Error(\"D2 instance has been disposed\");\n    }\n    await this.ready;\n    if (this.disposed) {\n      throw new Error(\"D2 instance has been disposed\");\n    }\n    return new Promise((resolve, reject) => {\n      const id = this.nextRequestId++;\n      this.pendingRequests.set(id, { resolve, reject });\n      try {\n        this.worker.postMessage({ id, type, data });\n      } catch (error) {\n        this.pendingRequests.delete(id);\n        reject(error);\n      }\n    });\n  }\n\n  async compile(input, options = {}) {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2js/js/src/index.js#L79-L115","documentation":"The D2 JS client throws \"D2 instance has been disposed\" from sendMessage when you call any API method (compile, render, encode, decode, version, jsVersion) after dispose() was invoked. dispose() sets this.disposed = true, terminates the underlying worker, and rejects all pending requests, so the instance can no longer forward messages. This first synchronous check at index.js:97 is a fast-fail guard before awaiting readiness.","triggerScenarios":"Calling d2.compile()/render()/encode()/decode()/version()/jsVersion() after d2.dispose() has been called on the same instance, including calls already queued in a promise chain that resolve after disposal.","commonSituations":"Component unmount / shutdown handlers disposing a shared D2 instance while async work is still issuing requests; double-use of a pool or cache returning a disposed instance; race between dispose() and an in-flight compile.","solutions":["Create a new D2 instance after dispose() instead of reusing the old one","Guard calls: track disposal yourself and skip the request if the instance is disposed","Reorder logic so dispose() only runs after all awaited API calls finish","Ensure the object you hold a reference to is the live instance (avoid caching disposed instances in a registry/pool)"],"exampleFix":"// before\nconst d2 = new D2();\nawait d2.dispose();\nconst svg = await d2.compile('x -> y'); // throws\n// after\nconst d2 = new D2();\nawait d2.dispose();\nconst d2v2 = new D2();\nconst svg = await d2v2.compile('x -> y');","handlingStrategy":"try-catch","validationCode":"if (d2.disposed) {\n  throw new Error('Cannot call D2: instance already disposed');\n}\nconst svg = await d2.compile('a -> b');","typeGuard":"function isUsable(d2) {\n  return d2 instanceof D2 && !d2.disposed && d2.worker !== undefined;\n}","tryCatchPattern":"try {\n  const svg = await d2.compile(src);\n} catch (err) {\n  if (err.message === 'D2 instance has been disposed') {\n    d2 = new D2(); // recreate and retry once\n  } else {\n    throw err;\n  }\n}","preventionTips":["Only call dispose() in definitive teardown paths (page unload, server shutdown)","Never reuse a D2 instance after dispose(); create a new one","Track in-flight API calls and await them before disposing","Avoid caching D2 instances in pools/registries without a disposed check"],"tags":["disposed-instance","lifecycle","worker","javascript"],"backgroundTag":"use-after-dispose","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}