Skip to content

Error Handling

All HTTP errors from HttpClient are wrapped in HttpClientError, a custom error class that includes the HTTP status code when applicable.

import { HttpClient, HttpClientError } from '@http-client-toolkit/core';
const client = new HttpClient({ cache: new InMemoryCacheStore() });
try {
const data = await client.get('https://api.example.com/user/1');
} catch (error) {
if (error instanceof HttpClientError) {
console.log(error.message); // Error description
console.log(error.statusCode); // HTTP status code (e.g. 404, 500)
console.log(error.data); // Parsed response body (if any)
console.log(error.headers); // Response headers
}
}

Use errorHandler to convert HTTP errors into your own domain types. The handler receives a typed HttpErrorContext with the response status, parsed body, and headers — no casting required.

errorHandler is only called for HTTP errors (non-2xx responses). Network failures and other non-HTTP errors are always wrapped in HttpClientError by the toolkit.

import { HttpClient, type HttpErrorContext } from '@http-client-toolkit/core';
class NotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = 'NotFoundError';
}
}
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
) {
super(message);
this.name = 'ApiError';
}
}
const client = new HttpClient(stores, {
errorHandler: (context) => {
console.error(`Request to ${context.url} failed`);
if (context.response.status === 404) {
return new NotFoundError('Resource not found');
}
const bodyMessage =
typeof context.response.data === 'object' && context.response.data !== null
? (context.response.data as { message?: string }).message
: undefined;
return new ApiError(
bodyMessage ?? context.message,
context.response.status,
);
},
});

When throwOnRateLimit is true (default), rate limit violations throw HttpClientError:

try {
await client.get(url);
} catch (error) {
if (error instanceof HttpClientError) {
// Could be a rate limit error or a server 429
console.log(error.message);
}
}

Set throwOnRateLimit: false to wait instead of throwing:

const client = new HttpClient(stores, {
throwOnRateLimit: false,
maxWaitTime: 30_000, // Wait up to 30 seconds
});

When a request is cancelled via AbortSignal, the error mimics a browser AbortError:

const controller = new AbortController();
try {
const promise = client.get(url, { signal: controller.signal });
controller.abort();
await promise;
} catch (error) {
// error.name === 'AbortError'
console.log('Request cancelled');
}

When using deduplication, if the owner request fails, non-owner waiters receive undefined rather than a thrown error. This prevents cascading failures across deduplicated callers.