Profiles·Public

cross-fetch

semver>=3.0.0 <5.0.0postconditions12functions4last verified2026-06-12coverage score100%

Postconditions — what we check

  • fetch · network-error
    error
    Whennetwork failure occurs (DNS error, connection refused, socket closed, timeout)
    ThrowsTypeError
    Required handlingCaller MUST wrap await fetch() in a try-catch block to handle network-level TypeErrors. Unlike HTTP 4xx/5xx responses (which resolve normally), network failures throw TypeError and will cause unhandled promise rejection if uncaught. Additionally, callers should check response.ok for HTTP errors.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1][2]
  • fetch · http-error-unchecked
    error
    Whenfetch() is called and the server returns an HTTP error status (4xx or 5xx), but the caller does not check response.ok (or response.status) before processing the response body. fetch() resolves successfully even on 404 Not Found, 401 Unauthorized, 500 Internal Server Error, etc. The response.ok property is false for any status outside 200–299, but it must be explicitly checked. This is the most common antipattern: callers call await response.json() on a 404 or 500 response, which returns the error body (often HTML error page), then either fail on JSON.parse or silently process the error as data.
    ThrowsDoes not throw — fetch() resolves. But: if the error response body is not valid JSON and response.json() is called, SyntaxError is thrown from response.json(). If response.ok is not checked, the error response is silently processed as success data.
    Required handlingAlways check response.ok after fetch() resolves: try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error ${response.status}: ${response.statusText}`); } const data = await response.json(); return data; } catch (error) { console.error('Request failed:', error); throw error; } For APIs that return structured error bodies, read the error body explicitly: if (!response.ok) { const errorBody = await response.json().catch(() => null); throw new Error(`HTTP ${response.status}: ${errorBody?.message ?? response.statusText}`); }
    costhighin prodsilent failureusers seelost datavisibilitysilent
    Sources[3][4]
  • fetch · abort-error
    warning
    Whenfetch() is called with options.signal set to an AbortController's signal, the AbortController is aborted (via controller.abort()) while the request is in-flight, and the caller does not handle the resulting AbortError. This is the standard pattern for implementing request timeouts: const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); const response = await fetch(url, { signal: controller.signal }); When the timeout fires, the fetch() promise rejects with AbortError. Callers using this timeout pattern but without try-catch cause unhandled rejections.
    ThrowsAbortError (name: 'AbortError') — in node-fetch (used by cross-fetch on Node.js), this is a custom class with name === 'AbortError', NOT a DOMException. Check: error.name === 'AbortError' (not instanceof DOMException). In browser environments, AbortError is a DOMException with name === 'AbortError'. The cross-environment check: error.name === 'AbortError' works in both.
    Required handlingWrap fetch() with signal in a try-catch and handle AbortError separately: const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); return await response.json(); } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { console.warn('Request timed out or was cancelled'); throw new Error('Request timeout'); } throw error; } Note: intentionally aborting a request (e.g., user cancelled navigation) should be handled gracefully, not re-thrown as an unexpected error.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3][5]
  • fetch · fetch-request-timeout-error
    warning
    Whenfetch() is called with the node-fetch-specific `{ timeout: <ms> }` init option (cross-fetch on Node.js), and the request takes longer than the timeout to establish a socket / receive headers. Distinct from AbortController abort: this is node-fetch's own setTimeout-driven path. Most commonly hit when: - A developer copies a snippet using `fetch(url, { timeout: 5000 })` expecting it to be cross-environment, not realizing it's a node-fetch extension - Background jobs poll slow upstreams without try-catch wrapping - Health-check loops use a short timeout without separating timeout from network errors The error is a FetchError subclass with name === 'FetchError' and type === 'request-timeout'.
    ThrowsFetchError (node-fetch extension class, NOT TypeError or AbortError) with: - name === 'FetchError' - type === 'request-timeout' - message === `network timeout at: ${url}` Source: node-fetch/lib/index.js line 1494. Catch with: error.name === 'FetchError' && error.type === 'request-timeout' On browsers (native fetch), the `timeout` option is silently dropped — this postcondition only fires in Node.js environments.
    Required handlingWrap fetch() with timeout option in a try-catch and distinguish request-timeout from network errors: try { const response = await fetch(url, { timeout: 5000 }); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); return await response.json(); } catch (error) { if (error.name === 'FetchError' && error.type === 'request-timeout') { throw new Error(`Upstream timed out after 5s: ${url}`); } if (error.name === 'AbortError') { throw new Error('Request was aborted'); } throw error; // Re-throw other network errors } Best practice: prefer AbortController for cross-environment compatibility. The `timeout` init option only works in node-fetch / cross-fetch on Node.js.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6][7]
  • fetch · fetch-body-timeout-error
    warning
    Whenfetch() is called with the node-fetch-specific `{ timeout: <ms> }` init option, the response headers arrive successfully (so fetch() resolves), but the body read (via .json(), .text(), .buffer(), etc.) takes longer than the timeout to fully stream. The body-timeout fires DURING the body read, not before fetch() resolves. Most commonly hit with: - Large response bodies (multi-MB JSON, big file downloads) - Slow upstream APIs that drip-feed body data - Streaming endpoints that hold the connection open This is a distinct error path from request-timeout — request-timeout fires before the response object is available; body-timeout fires after.
    ThrowsFetchError with: - name === 'FetchError' - type === 'body-timeout' - message === `Response timeout while trying to fetch ${url} (over ${timeout}ms)` Source: node-fetch/lib/index.js line 388. The error is thrown from inside response.json() / response.text() / response.buffer(), NOT from fetch() itself.
    Required handlingThe try-catch around response body methods must handle body-timeout distinctly: try { const response = await fetch(url, { timeout: 30000 }); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); const data = await response.json(); // body-timeout can fire here return data; } catch (error) { if (error.name === 'FetchError' && error.type === 'body-timeout') { throw new Error(`Body read timed out: response too slow`); } if (error.name === 'FetchError' && error.type === 'request-timeout') { throw new Error(`Request timed out`); } throw error; } For long-running streaming responses, set a larger timeout or use AbortController with explicit signal control. The `timeout` option arms BOTH timers from the same value — use it carefully when streaming large bodies.
    costmediumin prodimmediate exceptionusers seelost datavisibilityvisible
    Sources[6]
  • fetch · fetch-max-size-error
    warning
    Whenfetch() is called with the node-fetch-specific `{ size: <bytes> }` init option (cross-fetch on Node.js), and the response body exceeds the configured maximum. The size limit is checked incrementally during streaming — error is thrown the moment cumulative bytes exceed the threshold. Common use cases: - Untrusted upstream APIs where a 10GB response would OOM the process - User-controlled URL fetching (SSRF guardrail) - Memory-constrained Lambda / serverless environments When the limit is hit, the body method (.json(), .text(), .buffer()) rejects. Callers that set `size` without try-catch crash on oversized responses.
    ThrowsFetchError with: - name === 'FetchError' - type === 'max-size' - message === `content size at ${url} over limit: ${size}` Source: node-fetch/lib/index.js line 411. The error is thrown from inside the body read method, NOT from fetch() itself. Stream is aborted mid-read.
    Required handlingWrap body methods in try-catch and handle max-size as a distinct guardrail event: try { const response = await fetch(url, { size: 1_000_000 }); // 1MB cap if (!response.ok) throw new Error(`HTTP error: ${response.status}`); const data = await response.json(); return data; } catch (error) { if (error.name === 'FetchError' && error.type === 'max-size') { throw new Error(`Response too large: exceeded 1MB limit for ${url}`); } throw error; } For user-controlled URLs (SSRF defense), always set `size` AND wrap in try-catch. The `size` option only works in node-fetch / cross-fetch on Node.js — browsers silently ignore it.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6]
  • response.json · json-parse-error
    error
    Whenresponse.json() is called without a try-catch, and the response body is not valid JSON. Common triggers: - HTTP error responses (404/500) that return HTML error pages with "<!DOCTYPE html>" or "<html>" — causes "Unexpected token < in JSON" - APIs that return plain text (e.g., "OK", "Not found") instead of JSON - Empty response bodies (204 No Content) — causes "Unexpected end of JSON input" - APIs that sometimes return JSON and sometimes return plaintext errors This is especially common when response.ok is not checked first — callers blindly call response.json() on 500 Internal Server Error HTML pages.
    ThrowsIn browser environments: SyntaxError (native JSON.parse error) with message like "Unexpected token < in JSON at position 0". In Node.js via node-fetch: FetchError with type 'invalid-json' and message "invalid json response body at <url> reason: <parse error>". Both extend Error. Cross-environment safe catch: catch any error from response.json(). Check for: error instanceof SyntaxError || error.type === 'invalid-json'
    Required handlingAlways wrap response.json() in try-catch, or chain .catch(): try { const response = await fetch(url); if (!response.ok) { // Read body as text first to avoid JSON parse errors on error pages const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText.slice(0, 200)}`); } const data = await response.json(); return data; } catch (error) { if (error instanceof SyntaxError || error.type === 'invalid-json') { console.error('Response was not valid JSON:', error.message); throw new Error('API returned non-JSON response'); } throw error; } Best practice: check response.ok before calling response.json() to avoid parsing HTML error pages as JSON.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[8][1]
  • response.json · json-body-consumed-twice
    warning
    Whenresponse.json() (or any other body method: text(), blob(), etc.) is called a second time on the same Response object. Response bodies are streams — once read, they are consumed. Calling response.json() twice causes the second call to reject with TypeError "body used already". This occurs when: - Logging the response for debugging and then parsing it - Checking the body type before parsing - Multiple middleware/interceptor layers each trying to read the body
    ThrowsTypeError with message "body used already for: <url>" (node-fetch/cross-fetch Node.js). In browser: "Failed to execute 'json' on 'Response': body stream already read".
    Required handlingUse response.clone() before reading if the body needs to be read multiple times: const response = await fetch(url); const responseClone = response.clone(); // Clone before first read // Read original for logging const rawText = await response.text(); console.debug('Raw response:', rawText); // Parse the clone try { const data = await responseClone.json(); return data; } catch (error) { throw new Error(`JSON parse failed: ${error.message}`); } In most cases, pick one body method and stick to it. Reserve clone() for debug logging patterns.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[8][9]
  • response.text · text-abort-mid-stream
    warning
    Whenresponse.text() is called after fetch() resolves but the AbortController signal is aborted before the body is fully read. This is a two-phase abort: Phase 1 (request): if aborted before response headers, fetch() itself rejects. Phase 2 (body read): if aborted after headers but during body streaming, response.text() rejects with AbortError. Large response bodies are most susceptible — callers that abort after receiving headers but before the body is fully buffered hit this path.
    ThrowsAbortError (name: 'AbortError') — same type as the fetch() abort error. In node-fetch: custom AbortError class. In browser: DOMException. Cross-environment check: error.name === 'AbortError'.
    Required handlingWrap response.text() in the same try-catch as fetch(): const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); // response.text() can also throw AbortError if body read is slow const text = await response.text(); return text; } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('Request or body read timed out'); } throw error; }
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1][5]
  • response.text · text-body-consumed-twice
    warning
    Whenresponse.text() is called after any other body-read method (json(), blob(), arrayBuffer(), text()) has already consumed the response body on the same Response object. Response bodies are ReadableStream objects — once read (disturbed), they cannot be re-read. Common patterns that trigger this: - Reading the body once as text for logging, then attempting to parse as JSON - Middleware/interceptors that read the body before the handler does - Calling response.text() twice (e.g., validating content before processing) - Using text() as a fallback after json() throws a parse error on the same response
    ThrowsTypeError with message "body used already for: <url>" (node-fetch/cross-fetch Node.js). In browser: "Failed to execute 'text' on 'Response': body stream already read". In browser: also may throw TypeError "body is disturbed or locked". Cross-environment: always a TypeError.
    Required handlingUse response.clone() before reading the body if you need to read it multiple times: const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); // Clone BEFORE first read if you need the body in two places const responseForLog = response.clone(); try { const text = await response.text(); // Process text... return text; } catch (error) { // Safe: responseForLog was cloned before body was read const rawContent = await responseForLog.text().catch(() => '<unreadable>'); console.error('text() failed, raw content:', rawContent.slice(0, 200)); throw error; } Alternative: read the body once, then use the string for all purposes. Do NOT read the original response body after already reading it — clone first.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
  • response.textConverted · text-converted-missing-encoding-package
    error
    Whenresponse.textConverted() is called in a Node.js environment where the optional 'encoding' npm package is NOT installed. This is the most common failure mode: developers call textConverted() expecting charset detection to work, but the 'encoding' package is not in their dependencies. The failure happens silently until runtime — no build-time or import-time warning is given, because node-fetch loads 'encoding' in a try-catch at module initialization that swallows the module not found error. The call site is where the Error is thrown. Note: if running in a browser or React Native environment, textConverted() is not available (not in DOM fetch spec) — calling it would throw TypeError 'not a function'.
    ThrowsError (not TypeError) with message: "The package `encoding` must be installed to use the textConverted() function" Source: node-fetch/lib/index.js convertBody() line 446. This is a plain Error, not a subclass — catch with: error instanceof Error and check error.message includes 'encoding must be installed'.
    Required handlingOption A — Install the encoding package as a production dependency: npm install encoding Then wrap in try-catch to handle body stream errors: try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); const text = await response.textConverted(); return text; } catch (error) { if (error.message && error.message.includes('encoding must be installed')) { throw new Error('Charset detection requires the "encoding" package: npm install encoding'); } throw error; } Option B — Use response.text() instead (always available, assumes UTF-8): // For most modern APIs, UTF-8 is safe const text = await response.text(); Option C — Manual charset detection (dependency-free): const buffer = await response.buffer(); const contentType = response.headers.get('content-type') || ''; const charsetMatch = /charset=([^;]*)/i.exec(contentType); const charset = charsetMatch ? charsetMatch[1].trim() : 'utf-8'; const text = buffer.toString(charset as BufferEncoding);
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[12]
  • response.textConverted · text-converted-body-consumed-twice
    warning
    Whenresponse.textConverted() is called after any other body-read method (json(), text(), blob(), arrayBuffer(), buffer(), or a previous textConverted() call) has already consumed the response body on the same Response object. Response bodies are ReadableStream objects that can only be read once. After the first call, the body is "disturbed" and any subsequent read attempt throws TypeError. This error is identical to json-body-consumed-twice and text-body-consumed-twice — it's the same underlying consumeBody() TypeError path in node-fetch.
    ThrowsTypeError with message "body used already for: <url>" (node-fetch/cross-fetch Node.js). In browser: not applicable (textConverted() is Node.js only).
    Required handlingUse response.clone() before the first body read if you need to call textConverted() after another body method: const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); // Clone before first read const clone = response.clone(); // Read for logging const rawText = await response.text(); console.debug('Raw bytes:', Buffer.byteLength(rawText), 'bytes'); // Use textConverted() on the clone try { const decoded = await clone.textConverted(); return decoded; } catch (error) { if (error.message && error.message.includes('encoding must be installed')) { throw new Error('Install the "encoding" package: npm install encoding'); } throw error; }
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[12]

Sources

Every postcondition cites at least one of these. Numbered to match the footnotes above.

  1. [1]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
  2. [2]github.com/lquixada/cross-fetchhttps://github.com/lquixada/cross-fetch
  3. [3]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
  4. [4]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Response/ok
  5. [5]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/AbortController
  6. [6]github.com/node-fetch/node-fetchhttps://github.com/node-fetch/node-fetch/blob/2.x/README.md
  7. [7]github.com/node-fetch/node-fetchhttps://github.com/node-fetch/node-fetch/blob/2.x/ERROR-HANDLING.md
  8. [8]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Response/json
  9. [9]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Response/clone
  10. [10]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Response/text
  11. [11]developer.mozilla.org/en-US/docshttps://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#locked_and_disturbed_streams
  12. [12]raw.githubusercontent.com/node-fetch/node-fetchhttps://raw.githubusercontent.com/node-fetch/node-fetch/2.x/README.md
Need a different package?
Request a profile