Use this approach when your application needs different handling for an HTTP error response, a failed connection, or a timeout. ky treats non-2xx responses as errors by default and retries eligible failures before rejecting the request.
The samples run in a browser or a Node.js project with Fetch support. Install Ky in your project:
bashnpm install ky
1. Distinguish the failure and read its data
An HTTPError gives you response, request, normalized options, and pre-parsed data. Use isHTTPError, isNetworkError, and isTimeoutError to narrow the caught value before accessing error-specific properties.
NetworkErrorrepresents a network failure, such as DNS failure, connection refusal, or being offline. Readrequest.urland the original error incause; a fetch-phase network failure has no HTTP response.TimeoutErroridentifies an exceeded timeout and gives you the affectedrequest.isKyErrorcatches other failures in Ky's HTTP lifecycle. Keep a separate fallback for errors outside that lifecycle.
This request targets a nonexistent GitHub API endpoint. If the service returns a non-2xx response, the sample logs the HTTP status and parsed error body; if it returns a successful JSON response, the sample logs the response data instead. retry: 0 disables retries so the example handles one attempt.
tsimport ky, {isHTTPError, isNetworkError, isTimeoutError, isKyError} from 'ky';
void (async () => {
try {
const data = await ky.get('https://api.github.com/ky-error-example-not-found', {
retry: 0,
timeout: 5000,
}).json();
console.log('Response data:', data);
} catch (error) {
if (isHTTPError(error)) {
console.error('HTTP status:', error.response.status);
console.error('Content type:', error.response.headers.get('content-type'));
console.error('Error body:', error.data);
} else if (isNetworkError(error)) {
console.error('Network failure:', error.request.url, error.cause);
} else if (isTimeoutError(error)) {
console.error('Timeout:', error.request.url);
} else if (isKyError(error)) {
console.error('Ky failure:', error.message);
} else {
console.error('Other failure:', error);
}
}
})();
Ky parses JSON error bodies based on Content-Type, using parseJson when supplied or JSON.parse otherwise. Other content types produce text. data can be undefined when the body is empty, unreadable, too large, fails parsing, or exceeds the error-data read/parse timeout. Check its shape before reading fields.
Use the caught error's pre-parsed body for recovery decisions; see Request lifecycle for body consumption and response metadata.
Response-size failures require the next release
Ky 2.1.0 does not include maxResponseSize, ResponseSizeError, or isResponseSizeError. These APIs require the next release; do not import them in a 2.1.0 project.
With that release, maxResponseSize limits consumed response-stream bytes after decompression, independently of Content-Length. Exceeding it throws ResponseSizeError without automatic retries. Use isResponseSizeError() to distinguish it, then read error.maxResponseSize for the configured byte limit and error.request for the request. This is a body-size limit, not an HTTP status failure or a total-memory limit.
2. Treat expected HTTP errors as normal responses
Set throwHttpErrors: false when checking resource availability and expecting an error response. The call returns the response, leaving you to inspect ok or status and consume its body.
tsimport ky from 'ky';
void (async () => {
try {
const response = await ky.get('https://api.github.com/ky-error-example-not-found', {
throwHttpErrors: false,
});
if (response.status === 404) {
console.log('The resource is unavailable.');
} else if (!response.ok) {
console.error('Unexpected HTTP status:', response.status);
} else {
console.log('Response body:', await response.text());
}
} catch (error) {
console.error('The request failed without a normal HTTP response:', error);
}
})();
Disabling HTTP errors also disables status-based automatic retries. Ky considers those error responses successful. Keep throwHttpErrors enabled when relying on status-based retries; disable it only when error responses belong to normal application flow. Network failures and timeouts can still reject the call.
For selective handling, pass a function such as status => status !== 404. Ky then returns 404 responses normally but throws for other non-2xx statuses. Prefer the boolean form unless you need that distinction.
3. Customize the error before it reaches your catch block
Use hooks.beforeError to modify an error right before Ky throws it. The hook receives request, normalized options, error, and retryCount, and returns an Error or a promise of one. retryCount is 0 for the initial attempt and increments with each retry.
The example follows the error body's message field, when it is a string, and adds the HTTP status. Returning the same error preserves its HTTP-specific properties for the catch block. If the body lacks that field, the original message remains unchanged.
tsimport ky, {isHTTPError} from 'ky';
const api = ky.extend({
hooks: {
beforeError: [
({error}) => {
if (
isHTTPError(error)
&& typeof error.data === 'object'
&& error.data !== null
&& 'message' in error.data
&& typeof error.data.message === 'string'
) {
error.message = `${error.data.message} (${error.response.status})`;
}
return error;
},
],
},
});
void (async () => {
try {
await api.get('https://api.github.com/ky-error-example-not-found').json();
} catch (error) {
if (isHTTPError(error)) {
console.error(error.message);
} else {
console.error(error);
}
}
})();
error.data is populated before beforeError runs. Use the shortcut form await ky(url).json() to send body-read network and timeout failures through this hook as well. Errors from reading an already returned response are outside the request lifecycle.
Options that matter
| Option | Type | Default | What it does |
|---|---|---|---|
throwHttpErrors | boolean | ((status: number) => boolean) | true | Controls whether non-2xx responses throw an HTTP error. |
hooks.beforeError | Array of functions returning Error | Promise<Error> | [] | Modifies or replaces the error before it is thrown. |
timeout | number | false | 10000 | Sets the per-attempt timeout in milliseconds; shortcuts also use it as a separate body-read timeout. |
totalTimeout | number | false | false | Bounds the entire operation, including retries and delays. It does not bound beforeError hooks. |
Handle schema rejection separately
When using .json(schema), handle SchemaValidationError explicitly with error instanceof SchemaValidationError and read its issues. It does not extend KyError and isKyError() does not match it: the request succeeded, but your schema rejected the data. See Send and validate JSON for the validation sample.
Related
- Retry failed requests — configure which failures get another attempt.
- Request lifecycle — choose the hook for each stage.
- Cancel requests — cancel with an abort signal.
Was this page helpful?