`@alwatr/fetch` is an enhanced, lightweight, and dependency-free wrapper for the native `fetch` API. It provides modern features like caching strategies, request retries, timeouts, and intelligent duplicate request handling, all in a compact package.
npm install @alwatr/fetch@alwatr/fetch is an enhanced, lightweight, and dependency-free wrapper for the native fetch API. It provides modern features like caching strategies, request retries, timeouts, and intelligent duplicate request handling, all in a compact package.
It's designed to be a drop-in replacement for the standard fetch to instantly upgrade your application's network layer.
- Go-Style Error Handling: Returns a tuple [Response, null] on success or [null, FetchError] on failure—no exceptions thrown.
- Retry Pattern: Automatically retries failed requests on timeouts or server errors (5xx).
- Request Timeout: Aborts requests that take too long to complete.
- Duplicate Handling: Prevents sending identical parallel requests, returning a single response for all callers.
- Caching Strategies: Leverages the browser's Cache API with strategies like stale_while_revalidate.
- Simplified API: Send JSON and URL parameters with ease using bodyJson and queryParams.
- TypeScript First: Written entirely in TypeScript for a great developer experience.
Install the package using your preferred package manager:
``bashnpm
npm i @alwatr/fetch
Quick Start
Import the
fetch function and use it with tuple destructuring for elegant error handling. The function returns [Response, null] on success or [null, FetchError] on failure—no exceptions are thrown.`typescript
import {fetch} from '@alwatr/fetch';async function fetchProducts() {
console.log('Fetching product list...');
const [response, error] = await fetch('/api/products', {
queryParams: {limit: 10, category: 'electronics'},
cacheStrategy: 'stale_while_revalidate',
timeout: '5s',
});
if (error) {
console.error('Failed to fetch products:', error.message);
console.error('Error reason:', error.reason);
return;
}
// At this point, response is guaranteed to be valid and ok
const data = await response.json();
console.log('Products:', data);
}
fetchProducts();
`Error Handling
@alwatr/fetch uses a Go-style tuple return pattern instead of throwing exceptions. This provides explicit, type-safe error handling.$3
`typescript
type FetchResponse = Promise<[Response, null] | [null, FetchError]>;
`- Success:
[Response, null] - The response is guaranteed to have response.ok === true
- Failure: [null, FetchError] - Contains detailed information about what went wrong$3
All errors are returned as
FetchError instances, which provide rich context about the failure:`typescript
class FetchError extends Error {
reason: FetchErrorReason; // Specific error reason
response?: Response; // The HTTP response (if available)
data?: unknown; // Parsed response body (if available)
}
`$3
The
reason property indicates why the request failed:-
'http_error': HTTP error status (e.g., 404, 500)
- 'timeout': Request exceeded the timeout duration
- 'cache_not_found': Resource not found in cache (when using cache_only)
- 'network_error': Network-level error (e.g., DNS failure, connection refused)
- 'aborted': Request was aborted via AbortSignal
- 'unknown_error': Unspecified error$3
`typescript
const [response, error] = await fetch('/api/user/profile', {
bearerToken: 'jwt-token',
});if (error) {
switch (error.reason) {
case 'http_error':
console.error(
HTTP ${error.response?.status}:, error.data);
break;
case 'timeout':
console.error('Request timed out. Please try again.');
break;
case 'network_error':
console.error('Network error. Check your connection.');
break;
case 'cache_not_found':
console.error('Data not available offline.');
break;
default:
console.error('Request failed:', error.message);
}
return;
}// Safe to use response here
const userData = await response.json();
`API and Options
The
fetch function takes a url string and an options object. The options object extends the standard RequestInit and adds several custom options for enhanced control.| Option | Type | Default | Description |
| :------------------- | :---------------------------------------------- | :--------------- | :--------------------------------------------------------------------------------------------- |
|
method | HttpMethod | 'GET' | The HTTP request method. |
| headers | HttpRequestHeaders | {} | An object representing the request's headers. |
| timeout | Duration | 8_000 (8s) | Request timeout in milliseconds or as a duration string (e.g., '5s'). Set to 0 to disable. |
| retry | number | 3 | Number of retries if the request fails with a server error (5xx) or times out. |
| retryDelay | Duration | 1_000 (1s) | Delay between retry attempts in milliseconds or as a duration string. |
| removeDuplicate | 'never' \| 'always' \| 'until_load' \| 'auto' | 'never' | Strategy for handling identical parallel requests. body is included for uniqueness. |
| cacheStrategy | 'network_only' \| 'network_first' \| ... | 'network_only' | Caching strategy using the browser's Cache API. |
| cacheStorageName | string | 'fetch_cache' | Custom name for the CacheStorage instance. |
| revalidateCallback | (response: Response) => void | undefined | Callback executed with the new response when using stale_while_revalidate strategy. |
| bodyJson | Json | undefined | A JavaScript object sent as the request body. Sets Content-Type to application/json. |
| queryParams | Dictionary | undefined | An object of query parameters appended to the URL. |
| bearerToken | string | undefined | A bearer token added to the Authorization header. |
| alwatrAuth | {userId: string; userToken: string} | undefined | Alwatr-specific authentication credentials. |... and all other standard
RequestInit properties like signal, credentials, etc.---
Features in Detail
$3
The
queryParams option simplifies adding search parameters to your request URL.`typescript
// This will make a GET request to: /api/users?page=2&sort=asc
const [response, error] = await fetch('/api/users', {
queryParams: {page: 2, sort: 'asc'},
});if (error) {
console.error('Failed to fetch users:', error.message);
return;
}
const users = await response.json();
`$3
Use
bodyJson to send a JavaScript object as a JSON payload. The Content-Type header is automatically set to application/json.`typescript
// This will make a POST request to /api/orders with a JSON body
const [response, error] = await fetch('/api/orders', {
method: 'POST',
bodyJson: {
productId: 'xyz-123',
quantity: 2,
},
});if (error) {
console.error('Failed to create order:', error.message);
return;
}
const order = await response.json();
console.log('Order created:', order);
`$3
Set a timeout for your requests. If the request takes longer than the specified duration, it will be aborted and return a
FetchError with reason: 'timeout'.`typescript
const [response, error] = await fetch('/api/slow-endpoint', {
timeout: '2.5s', // You can use duration strings
});if (error) {
if (error.reason === 'timeout') {
console.error('Request timed out after 2.5 seconds');
}
return;
}
`$3
The fetch operation will automatically retry on server errors (5xx status codes) or timeouts.
`typescript
// Retry up to 5 times, with a 2-second delay between each attempt
const [response, error] = await fetch('/api/flaky-service', {
retry: 5,
retryDelay: '2s',
});if (error) {
console.error('Request failed after 5 retries:', error.message);
return;
}
const data = await response.json();
`$3
The
removeDuplicate option prevents multiple identical requests from being sent simultaneously. The uniqueness of a request is determined by its method, URL, and body.-
'never' (default): Does nothing.
- 'until_load': Caches the Promise of a request until it resolves. Subsequent identical requests will receive a clone of the first response.
- 'always': Caches the response indefinitely (for the lifetime of the application).
- 'auto': Uses 'until_load' if the Cache API is available, otherwise 'always'.`typescript
// Both calls will result in only ONE network request.
// The second call will receive the response from the first.
const results = await Promise.all([
fetch('/api/data', {removeDuplicate: 'until_load'}),
fetch('/api/data', {removeDuplicate: 'until_load'}),
]);// Both results will have the same response or error
const [response1, error1] = results[0];
const [response2, error2] = results[1];
`$3
Leverage the browser's Cache API with
cacheStrategy.-
'network_only' (default): Standard fetch behavior; no caching.
- 'cache_first': Serves from cache if available. Otherwise, fetches from the network and caches the result.
- 'network_first': Fetches from the network first. If the network fails, it falls back to the cache.
- 'cache_only': Only serves from cache; returns an error if not found.
- 'update_cache': Fetches from network and updates the cache.
- 'stale_while_revalidate': The fastest strategy. It serves stale content from the cache immediately while sending a network request in the background to update the cache for the next time.`typescript
// Serve news from cache instantly, but update it in the background for the next visit.
const [response, error] = await fetch('/api/news', {
cacheStrategy: 'stale_while_revalidate',
revalidateCallback: (freshResponse) => {
console.log('Cache updated with fresh data!');
// You can use freshResponse to update the UI if needed
},
});if (error) {
console.error('Failed to load news:', error.message);
return;
}
const news = await response.json();
`$3
Easily add authentication headers with
bearerToken or the alwatrAuth scheme.`typescript
// Using a Bearer Token
const [response, error] = await fetch('/api/secure/data', {
bearerToken: 'your-jwt-token-here',
});if (error) {
if (error.response?.status === 401) {
console.error('Authentication failed. Please log in again.');
}
return;
}
const data = await response.json();
// Using Alwatr's authentication scheme
const [response2, error2] = await fetch('/api/secure/data', {
alwatrAuth: {
userId: 'user-id',
userToken: 'user-auth-token',
},
});
``The following companies, organizations, and individuals support Nanolib's ongoing maintenance and development. Become a Sponsor to get your logo on our README and website.
Contributions are welcome\! Please read our contribution guidelines before submitting a pull request.