denoland/deno · error · TypeError

Body is unusable

Error message

Body is unusable

What it means

Thrown by Response.clone() when the response's body stream has already been disturbed (read, piped, cancelled, or locked by a reader) or has errored. The fetch spec requires clone() to tee an untouched stream so both copies stay readable, so a used body cannot be cloned. Deno first materializes lazy static bodies (responses created from strings/ArrayBuffers), so this only fires for genuinely consumed or errored streams.

Source

Thrown at ext/fetch/23_response.js:841

    return this[_response].statusMessage;
  }

  /**
   * @returns {Headers}
   */
  get headers() {
    webidl.assertBranded(this, ResponsePrototype);
    return responseHeaders(this);
  }

  /**
   * @returns {Response}
   */
  clone() {
    webidl.assertBranded(this, ResponsePrototype);
    materializeLazyStaticBody(this);
    if (this[_body] && this[_body].unusable()) {
      throw new TypeError("Body is unusable");
    }
    const second = webidl.createBranded(Response);
    const newRes = cloneInnerResponse(this[_response]);
    initializeResponseBase(second, newRes, responseHeaderGuard(this));
    return second;
  }

  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    return inspect(
      createFilteredInspectProxy({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this),
        keys: [
          "body",
          "bodyUsed",
          "headers",
          "ok",
          "redirected",

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Clone immediately after fetch() resolves, before any read: const copy = res.clone()
  2. Check res.bodyUsed === false before calling clone()
  3. If the body is already consumed, re-issue the fetch instead of cloning
  4. For manual splitting of a partially read stream, use res.body.tee() on a still-locked-free stream instead of clone()

Example fix

// before
const data = await res.json();
const copy = res.clone(); // TypeError: Body is unusable

// after
const copy = res.clone(); // clone before any read
const data = await res.json();
Defensive patterns

Strategy: validation

Validate before calling

function isCloneable(res) {
  return !res.bodyUsed && (!res.body || !res.body.locked);
}
// usage
if (isCloneable(res)) {
  const copy = res.clone();
} else {
  // body already consumed: refetch or reuse parsed data
}

Type guard

function isCloneable(res: Response): boolean {
  return !res.bodyUsed && (res.body === null || !res.body.locked);
}

Try / catch

try {
  const copy = res.clone();
} catch (err) {
  if (err instanceof TypeError && err.message === "Body is unusable") {
    // body already consumed; refetch or reuse the parsed value
  } else throw err;
}

Prevention

When it happens

Trigger: Calling res.text(), res.json(), res.arrayBuffer(), res.formData(), res.body.getReader().read(), or res.body.cancel() on a Response and then calling res.clone() on the same Response; or cloning a streaming response whose body errored mid-transfer.

Common situations: Logging or caching middleware that parses the body first and tries to clone afterwards; retry wrappers that clone after inspection; interceptor chains where an earlier layer already consumed the body; tests that await the body twice.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/934eec92258a64db. Report an issue: GitHub.