node-fetch
semver
>=2.0.0postconditions16functions8last verified2026-06-23coverage score100%Postconditions: what we check
- fetch · fetch-rejects-on-network-errorerrorWhennetwork failure (DNS resolution failure, connection refused, timeout, etc.)Throws
Promise rejection with FetchError (error.type='system', error.code like ENOTFOUND, ECONNREFUSED, ETIMEDOUT)Required handlingCaller MUST use try-catch to handle Promise rejections from fetch(). Network-level failures (DNS errors, connection refused, timeouts) reject the Promise. CRITICAL: fetch() does NOT reject on HTTP 4xx/5xx status codes - those are successful responses that must be checked with response.ok. Recommended pattern: try { const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); } catch (error) { /* handle network errors */ }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - fetch · fetch-rejects-on-aborterrorWhenrequest is cancelled via AbortSignal before or during the requestThrows
Promise rejection with AbortError (error.name='AbortError', error.type='aborted')Required handlingWhen using AbortController to cancel requests, the caller MUST handle AbortError separately from FetchError. AbortError is thrown when signal.abort() is called. Pattern: try { const res = await fetch(url, { signal }); } catch (err) { if (err.name === 'AbortError') { /* handle cancellation */ } else throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - fetch · fetch-rejects-on-redirect-errorwarningWhenredirect:error option set and server responds with redirect, OR redirect limit (default 20) exceededThrows
Promise rejection with FetchError (error.type='no-redirect' or error.type='max-redirect')Required handlingWhen fetch() is called with redirect:'error', any redirect response causes rejection with FetchError (error.type='no-redirect'). When redirect limit (default 20) is exhausted, rejects with FetchError (error.type='max-redirect'). Both must be caught.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - fetch · fetch-http-error-not-thrownerrorWhenserver responds with HTTP 4xx or 5xx status codeThrows
DOES NOT throw — resolves with Response object where response.ok === falseRequired handlingCRITICAL FOOTGUN: Unlike axios, node-fetch does NOT reject on HTTP error status codes. HTTP 4xx/5xx resolve successfully to a Response with response.ok === false. Callers MUST explicitly check response.ok or response.status before processing body. Failure silently processes error responses (HTML error pages, error JSON) as valid data. Pattern: if (!response.ok) throw new Error(`HTTP error ${response.status}`);costhighin prodsilent failureusers seelost datavisibilitysilentSources[2] - response.json · response-json-throws-on-invalid-jsonerrorWhenresponse body is not valid JSON (HTML error pages, plain text, empty body from 204 responses)Throws
SyntaxError from JSON.parse() — 'Unexpected token < in JSON at position 0' or similarRequired handlingresponse.json() calls JSON.parse() internally. If the server returns HTML (e.g., error page, WAF block, load balancer error), SyntaxError is thrown. Caller MUST wrap in try-catch. Combine with response.ok check to handle HTTP errors before attempting to parse body. Pattern: if (!response.ok) throw new Error(...); const data = await response.json();costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - response.json · response-json-throws-on-max-sizewarningWhenresponse body size exceeds the size limit set in RequestInit.size optionThrows
FetchError (error.type='max-size') — 'content size at <url> over limit: <size>'Required handlingIf fetch() was called with a size limit option and the response body exceeds it, response.json() throws FetchError with error.type='max-size'. Must be caught with try-catch around response.json().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - response.json · response-json-throws-on-body-usedwarningWhenresponse.json() called after body stream has already been consumed by any body-reading methodThrows
TypeError — 'body used already for: <url>'Required handlingResponse bodies can only be consumed once. Calling response.json() after any other body-consuming method (json, text, blob, arrayBuffer, formData) throws TypeError. Use response.clone() to read the body multiple times. Pattern: const cloned = response.clone(); const data = await cloned.json();costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - response.text · response-text-throws-on-stream-errorerrorWhenresponse body stream encounters a system-level error during reading (premature close, network drop after headers received)Throws
FetchError (error.type='system') — 'Invalid response body while trying to fetch <url>'Required handlingEven after a successful fetch() (headers received, Promise resolved), the body stream can fail during reading. response.text() MUST be wrapped in try-catch. FetchError with type='system' indicates a stream-level I/O error. Common mistake: wrapping only fetch() in try-catch, leaving response.text() unguarded.costmediumin prodimmediate exceptionusers seedegraded performancevisibilityvisibleSources[1] - response.text · response-text-throws-on-premature-closeerrorWhenserver closes the connection before the full body is receivedThrows
FetchError — 'Premature close of server response while trying to fetch <url>'Required handlingIf the server closes the TCP connection before sending the full response body, response.text() throws FetchError. This occurs after headers are received but before body completes — distinct from initial fetch() network failure. Must be caught with try-catch around response.text().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - response.blob · response-blob-throws-on-stream-errorerrorWhenresponse body stream fails during reading (system error, premature close, max-size exceeded)Throws
FetchError (error.type='system' or 'max-size') — same errors as response.text()Required handlingresponse.blob() delegates to arrayBuffer() which uses the same consumeBody() stream path. Must be wrapped in try-catch to handle FetchError on stream errors. Same error types apply as response.text() and response.arrayBuffer().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - response.arrayBuffer · response-arraybuffer-throws-on-stream-errorerrorWhenresponse body stream fails during reading (system error, premature close, or size limit exceeded)Throws
FetchError (error.type='system') or FetchError (error.type='max-size') if size limit exceededRequired handlingresponse.arrayBuffer() uses the same consumeBody() stream-reading path. IMPORTANT for file downloads: max-size option on the original fetch() call will cause FetchError here if the file is larger than the limit. Must be wrapped in try-catch.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - response.formData · response-formdata-throws-on-missing-content-typeerrorWhenresponse has no Content-Type header (server omitted it)Throws
TypeError — 'Cannot read properties of null (reading 'startsWith')' from ct.startsWith()Required handlingresponse.formData() reads response.headers.get('content-type') and calls .startsWith() on the result. If the server response omits Content-Type, this returns null and dereferencing throws TypeError. Wrap in try-catch or guard with `if (response.headers.get('content-type'))` before calling formData().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - response.formData · response-formdata-throws-on-stream-errorerrorWhenresponse body stream fails during reading (system error, premature close, size limit exceeded) — same FetchError surface as response.text()Throws
FetchError (error.type='system' or 'max-size') — body-consumption failures from consumeBody()Required handlingresponse.formData() reads the body via this.text() (for x-www-form-urlencoded) or the multipart parser (for multipart/form-data). Both paths funnel through consumeBody() which throws FetchError on stream-level I/O errors, premature close, or size-limit overrun. Must be wrapped in try-catch around the await.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - response.formData · response-formdata-throws-on-body-usedwarningWhenresponse.formData() called after body stream has already been consumed by any body-reading methodThrows
TypeError — 'body used already for: <url>'Required handlingResponse bodies can only be consumed once. Calling response.formData() after json(), text(), blob(), arrayBuffer(), or another formData() call throws TypeError. Use response.clone() to read the body multiple times.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - blobFrom · blobfrom-rejects-on-fs-stat-failureerrorWhenpath does not exist, is inaccessible (permission denied), or is otherwise unreadable by fs.promises.stat()Throws
Promise rejection with NodeJS.ErrnoException (error.code='ENOENT', 'EACCES', 'EISDIR', etc.)Required handlingblobFrom(path) calls fs.promises.stat(path) under the hood. Any filesystem failure (missing file ENOENT, permission denied EACCES, directory EISDIR) rejects the Promise with the underlying Node error. Caller MUST wrap in try-catch — common failure modes when uploading user-supplied paths or paths from configuration files that may have moved.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - fileFrom · filefrom-rejects-on-fs-stat-failureerrorWhenpath does not exist, is inaccessible (permission denied), or is otherwise unreadable by fs.promises.stat()Throws
Promise rejection with NodeJS.ErrnoException (error.code='ENOENT', 'EACCES', 'EISDIR', etc.)Required handlingfileFrom(path) calls fs.promises.stat(path) under the hood. Any filesystem failure (missing file ENOENT, permission denied EACCES, directory EISDIR) rejects the Promise with the underlying Node error. Same handling as blobFrom — wrap in try-catch when constructing upload bodies from user-supplied paths.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
Source code
- [1]github.com/node-fetch/node-fetch/blobnode-fetch/node-fetch · ERROR-HANDLING.md
- [2]github.com/node-fetch/node-fetch/blobnode-fetch/node-fetch · README.md
- [3]github.com/node-fetch/node-fetch/blobnode-fetch/node-fetch · body.js
- [4]github.com/node-fetch/fetch-blob/blobnode-fetch/fetch-blob · from.js
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: node-fetch
Package: node-fetch Contract Version: 1.0.0 Last Updated: 2026-02-26
Official Documentation
Error Handling Guide
- URL: https://github.com/node-fetch/node-fetch/blob/main/docs/ERROR-HANDLING.md
- Relevance: Official documentation on error handling, FetchError types, and network failures
- Key Points:
- All operational errors (except aborted requests) are rejected with FetchError
- Network errors have
error.type = 'system'with error.code (ENOTFOUND, ECONNREFUSED, ETIMEDOUT) - AbortError for cancelled requests
- Critical: HTTP 3xx-5xx responses are NOT exceptions and should be handled in then()
NPM Package Page
- URL: https://www.npmjs.com/package/node-fetch
- Relevance: Official package documentation and usage examples
- Key Points:
- Lightweight fetch() implementation for Node.js
- Promise-based API matching browser fetch()
- Supports HTTP/HTTPS requests
Common Error Patterns
Network-Level Errors (Reject Promise)
- DNS Resolution Failure - ENOTFOUND
- Connection Refused - ECONNREFUSED
- Timeout - ETIMEDOUT
- Network Unreachable - ENETUNREACH
- Aborted Requests - AbortError
HTTP-Level Errors (Do NOT Reject)
- HTTP 4xx (Client errors) - Response resolves successfully
- HTTP 5xx (Server errors) - Response resolves successfully
- Must check
response.okorresponse.statusmanually
Nark profile Rationale
Why fetch() Requires Error Handling
Network failures are common and unpredictable:
- DNS servers may be unavailable
- Target server may be down
- Network connectivity issues
- Firewall blocking connections
- Request timeouts
Failure to handle rejections leads to:
- Unhandled promise rejections
- Application crashes (in newer Node.js versions)
- Silent failures
- Poor user experience
Critical Gotcha: HTTP Status Codes
Unlike XMLHttpRequest or many HTTP libraries, fetch() does not reject on HTTP error status codes:
// This will NOT reject even if server returns 404 or 500
const response = await fetch('https://api.example.com/nonexistent');
// response.ok will be false, but Promise resolved successfully
Correct pattern:
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
} catch (error) {
// Handles both network errors AND HTTP errors (if you throw them)
console.error('Fetch failed:', error);
}
References
- node-fetch Error Handling Documentation
- Fetch API Error Handling - web.dev
- FetchError function - Snyk Advisor
- Handling Failed HTTP Responses With fetch()
Version Notes
- v2.x: Introduced Promise-based API
- v3.x: ESM-only, dropped CommonJS support
- Contract applies to v2.0.0+ (covers both v2 and v3 error behavior)
Need a different package?
Request a profile