try-fetch-catch
v1.0.2
Published
Go-style error handling for JavaScript that never throws. tryFetch returns [error, data, response] tuples, tryCatch returns [error, result] tuples.
Downloads
184
Maintainers
Readme
try-fetch-catch
Go-style error handling for JavaScript/TypeScript.
try-fetch-catch provides two small utilities that replace thrown exceptions with predictable tuples:
tryFetchwrapsfetch()and returns[error, data, response].tryCatchwraps any sync/async function and returns[error, result].
No thrown exceptions. No nested try/catch. No unhandled promise rejections. Just linear, easy-to-read code.
Install
npm install try-fetch-catchRequirements:
- Node.js 18+ (native
fetch) - Or any runtime that provides the Fetch API (modern browsers, Deno, Bun)
Import in your project:
import { tryFetch, tryCatch } from "try-fetch-catch";
// OR
const { tryFetch, tryCatch } = require("try-fetch-catch");Note: This package ships native ESM and CommonJS builds.
Why use try-fetch-catch
- 🎯 Clean code - Linear, no-throw control flow without nested try/catch boilerplate.
- 🔌 Drop-in
fetchreplacement - ReplacefetchwithtryFetchand accesserr+dataon the same line. - ⚡ Smart auto-parsing — Parses by the
ResponseContent-Type header automatically, with easy overrides or a custom parser when you need full control. - 🚫 No uncaught exceptions — Thrown exceptions are captured internally and returned as typed error values.
- 💪 TypeScript-friendly - Generics and exported types for predictable, typed results.
- 📦 Tiny & dependency-free - Minimal footprint, zero dependencies, low maintenance.
Example comparison
- Fetching Data
// BEFORE (Traditional fetch)
try {
const response = await fetch("/api/users/123");
if (!response.ok) throw new Error("HTTP Error");
const data = await response.json();
} catch (err) {
console.error(err);
}
// AFTER (tryFetch)
const [err, data] = await tryFetch("/api/users/123");
if (err) console.error(err);Key advantages: Linear, automatic HTTP & parse handling, fully predictable.
- Parsing Data
// BEFORE (Traditional JSON.parse)
try {
const user = JSON.parse(userDataString);
} catch (err) {
console.error(err);
}
// AFTER (tryCatch)
const [err, user] = tryCatch(JSON.parse, userDataString);
if (err) console.error(err);Key advantages: Works for sync/async, linear flow, no try/catch boilerplate.
Using tryFetch
tryFetch is a drop-in replacement for native fetch that never throws.
Instead of relying on thrown exceptions, it always returns a tuple where errors, data, and the response are explicit and predictable.
Core rule:
tryFetchnever throws. All failures are returned as data.
Basic Usage
Use tryFetch exactly like fetch, but destructure the result instead of wrapping it in try/catch.
import { tryFetch } from "try-fetch-catch";
const [err, user] = await tryFetch<User>(`/api/users/${id}`);
if (err) {
// handle error
return;
}
// `user` is safe and already parsed
renderUser(user);This pattern keeps control flow linear and makes error handling impossible to forget - a common source of bugs in the JavaScript world.
Error model
tryFetch always resolves to a tuple with one of the following shapes:
Success
[null, data, Response]HTTP error (non-2xx response)
[HttpError, data | null, Response]
(data is null if the response body can’t be parsed)Network / transport error (no response)
[NetworkError, null, undefined]Response parse error
[ParseError, null, Response]
(request succeeded, but body parsing failed)
A common error handling pattern:
const [err, data, res] = await tryFetch<User>(`/api/users/${id}`);
if (err?.status === 0) {
// Network error: no data and HTTP response
return showOfflineUI(err.message);
}
if (err) {
// HTTP error (4xx / 5xx)
return showErrorPage(err.status);
}
// Success - data is safe
renderUser(data);Best practice: Always check
errbefore usingdata.
Signature
tryFetch<T = unknown>(
input: RequestInfo | URL,
init?: RequestInit & { tryFetchOptions?: TryFetchOptions<T> },
): Promise<FetchResult<T>>Automatic response parsing
By default, tryFetch automatically parses the response body based on the Content-Type header:
- JSON (
application/json,+json) → parsed JSON - Text (
text/\*, XML types) → response.text() - Binary (
image/\*,application/octet-stream,application/pdf) →response.blob() - Form data (
multipart/form-data,application/x-www-form-urlencoded) →response.formData() - Unknown or missing type → reads text, then attempts a best-effort
JSON.parse - Empty bodies (204/205,
content-length: 0, or empty JSON) → null
In most cases, you never need to think about parsing — the returned data is already usable.
Note: On HTTP error responses, tryFetch still attempts to parse the response body so APIs can return structured error payloads.
Overriding the parser
Auto-parsing covers most APIs. When you need more control, you have two options.
Force a specific parse mode
const [err, text] = await tryFetch<string>("/api/raw", {
tryFetchOptions: { parseAs: "text" },
});Accepted values for parseAs:
"json" | "text" | "blob" | "arrayBuffer" | "formData"
Provide a custom parser
const [err, upper] = await tryFetch<string>("/api/greeting", {
tryFetchOptions: {
parser: async (response) => (await response.text()).toUpperCase(),
},
});If a custom parser throws, the error is returned as a ParseError.
Response consumption and preserveResponse
By default, tryFetch consumes the response body once while parsing it.
- Network error returns no
Response. - HTTP responses (success or error) return a
Responsewhose body is already consumed. - You can still safely access metadata like
status,statusText, andheaders.
If you need to read the response body more than once, enable preserveResponse:
const [err, data, res] = await tryFetch("/api/data", {
tryFetchOptions: { preserveResponse: true },
});
if (!err) {
const again = await res.json(); // body is still readable
}When preserveResponse is enabled, parsing is performed on a cloned Response so the returned Response remains readable.
tryFetchOptions reference
tryFetchOptions is passed inside the native fetch init object:
await tryFetch(url, {
...RequestInit,
tryFetchOptions: {
/* ... */
},
});| Option | Type | Accepted values | Default | Usage |
| ------------------ | ----------------------------------------- | ------------------------------------------------------------------- | ------------------------------ | ---------------------------------------------- |
| parseAs | ResponseParseAs | "json" | "text" | "blob" | "arrayBuffer" | "formData" | Auto-detect via Content-Type | tryFetchOptions: { parseAs: "text" } |
| parser | (response: Response) => T \| Promise<T> | Any function | none | tryFetchOptions: { parser: (r) => r.text() } |
| preserveResponse | boolean | true / false | false | tryFetchOptions: { preserveResponse: true } |
Notes:
parseroverridesparseAsandContent-Typeauto-parsing.preserveResponse: trueensures the returnedResponseremains readable.
Using tryCatch
tryCatch wraps any synchronous or asynchronous function and returns [error, result] instead of throwing.
It always resolves to a predictable tuple:
- Success:
[null, result] - Failure:
[Error, null]
Non-
Errorthrows are coerced toErrortype.
Examples
Asynchronous function:
// Direct function + arguments
const [err, user] = await tryCatch(loadUser, userId);
// OR using a callback
const [err2, user2] = await tryCatch(() => loadUser(userId));
if (err) return log.error(err.message);
log.info(user);Synchronous function:
// Direct function + arguments
const [err, user] = tryCatch(JSON.parse, userDataString);
// OR using a callback
const [err2, user2] = tryCatch(() => JSON.parse(userDataString));
if (err) return;
log.info(user);Types
This package exports a small set of TypeScript types for tryFetch.
import type {
FetchResult,
HttpError,
NetworkError,
ParseError,
TryFetchOptions,
TryFetchRequestInit,
ResponseParseAs,
} from "try-fetch-catch";FetchResult<T>
tryFetch always resolves to one of these tuple shapes:
- Success:
[null, T, Response] - HTTP error (non-2xx):
[HttpError, T | null, Response] - Parse error (2xx but body parsing failed):
[ParseError, null, Response] - Network/transport error (no Response):
[NetworkError, null, undefined]
TryFetchOptions<T> and ResponseParseAs
Use TryFetchOptions<T> under init.tryFetchOptions to force parsing (parseAs) or provide a custom parser.
TryFetchRequestInit<T>
Convenience type for the init parameter: native RequestInit plus tryFetchOptions.
tryCatch typing
tryCatch is intentionally minimal: it returns [err, result].
- Success:
[null, T] - Failure:
[Error, null]
Note: result can still be null on success if the wrapped function can return null (i.e. your T includes null).
Author
Sion Young
License
ISC
