Overview
The resilientFetch function is a drop-in replacement for the native fetch API that automatically integrates with abort signals from the resilient execution context. When used inside a function wrapped with withResilience and useAbortSignal: true, it automatically inherits the timeout and cancellation behavior.
Function Signature
function resilientFetch (
input : RequestInfo | URL ,
init ?: RequestInit
) : Promise < Response >
Parameters
input
RequestInfo | URL
required
The resource to fetch. Can be:
A string URL
A URL object
A Request object
Optional fetch options object. All standard fetch options are supported. HTTP method (GET, POST, PUT, DELETE, etc.)
Optional abort signal. If not provided, automatically uses the active signal from the current resilient execution context.
All other standard fetch options are supported (mode, credentials, cache, redirect, etc.)
Return Value
A Promise that resolves to the Response object, exactly like native fetch. The request will be automatically cancelled if the active abort signal is triggered.
Implementation Details
From src/index.ts:197-201, the function:
Checks if a signal is already provided in init.signal
If not, uses the active signal from the current resilient execution context
If no signal is available, falls back to standard fetch behavior
Merges the signal into the init options
export const resilientFetch = ( input : RequestInfo | URL , init ?: RequestInit ) => {
const signal = init ?. signal ?? activeSignal ;
if ( ! signal ) return fetch ( input , init );
return fetch ( input , { ... init , signal });
};
Examples
Basic Usage (Drop-in Replacement)
import { resilientFetch } from '@oldwhisper/resilience' ;
// Works exactly like regular fetch
const response = await resilientFetch ( '/api/users/123' );
const user = await response . json ();
Automatic Timeout Cancellation
When used inside a resilient function with timeout, the fetch is automatically cancelled:
import { withResilience , resilientFetch } from '@oldwhisper/resilience' ;
const fetchUserWithTimeout = withResilience (
async ( userId : string ) => {
// This fetch will be automatically cancelled after 3 seconds
const response = await resilientFetch ( `/api/users/ ${ userId } ` );
return await response . json ();
},
{
timeoutMs: 3000 ,
useAbortSignal: true // Enable automatic abort signal
}
);
try {
const user = await fetchUserWithTimeout ( '123' );
} catch ( error ) {
if ( error . message === 'TimeoutError' ) {
console . log ( 'Request timed out and was cancelled' );
}
}
Multiple Requests with Shared Cancellation
const fetchUserProfile = withResilience (
async ( userId : string ) => {
// All these requests share the same abort signal
const [ user , posts , comments ] = await Promise . all ([
resilientFetch ( `/api/users/ ${ userId } ` ). then ( r => r . json ()),
resilientFetch ( `/api/posts?userId= ${ userId } ` ). then ( r => r . json ()),
resilientFetch ( `/api/comments?userId= ${ userId } ` ). then ( r => r . json ())
]);
return { user , posts , comments };
},
{
timeoutMs: 5000 ,
useAbortSignal: true
}
);
// If timeout occurs, all three requests are cancelled automatically
Sequential Requests with Early Cancellation
const processWorkflow = withResilience (
async ( workflowId : string ) => {
// Step 1: Create resource
const createRes = await resilientFetch ( '/api/resources' , {
method: 'POST' ,
body: JSON . stringify ({ workflowId })
});
const resource = await createRes . json ();
// Step 2: Process resource
const processRes = await resilientFetch ( `/api/resources/ ${ resource . id } /process` , {
method: 'POST'
});
// Step 3: Get final status
const statusRes = await resilientFetch ( `/api/resources/ ${ resource . id } /status` );
return await statusRes . json ();
},
{
timeoutMs: 10000 ,
retries: 2 ,
useAbortSignal: true
}
);
// If timeout occurs at any step, the current request is cancelled
// and the function can retry from the beginning
With Custom Headers and Options
const apiCall = withResilience (
async ( endpoint : string , data : any ) => {
const response = await resilientFetch ( `/api/ ${ endpoint } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ token } `
},
body: JSON . stringify ( data ),
// Signal is automatically added, no need to pass it
});
if ( ! response . ok ) {
throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` );
}
return await response . json ();
},
{
timeoutMs: 5000 ,
retries: 3 ,
useAbortSignal: true ,
retryOn : ( error ) => {
// Retry on network errors and 5xx responses
return error instanceof TypeError ||
( error . message . startsWith ( 'HTTP 5' ));
}
}
);
Manual Abort Signal Override
You can still provide your own abort signal if needed:
const controller = new AbortController ();
const fetchWithCustomSignal = withResilience (
async () => {
// Uses the provided signal instead of the active one
return await resilientFetch ( '/api/data' , {
signal: controller . signal
});
},
{
timeoutMs: 5000 ,
useAbortSignal: true
}
);
// You can abort manually
setTimeout (() => controller . abort (), 2000 );
Polling with Automatic Cancellation
const pollForCompletion = withResilience (
async ( jobId : string ) => {
while ( true ) {
const response = await resilientFetch ( `/api/jobs/ ${ jobId } ` );
const job = await response . json ();
if ( job . status === 'completed' ) {
return job . result ;
}
if ( job . status === 'failed' ) {
throw new Error ( 'Job failed' );
}
// Wait before next poll
await sleep ( 1000 );
}
},
{
timeoutMs: 60000 , // 1 minute timeout
useAbortSignal: true
}
);
// Both fetch and sleep are cancelled when timeout occurs
File Upload with Timeout
const uploadFile = withResilience (
async ( file : File ) => {
const formData = new FormData ();
formData . append ( 'file' , file );
const response = await resilientFetch ( '/api/upload' , {
method: 'POST' ,
body: formData
// Automatically cancelled on timeout
});
return await response . json ();
},
{
timeoutMs: 30000 , // 30 second timeout for uploads
retries: 1 ,
useAbortSignal: true
}
);
Error Handling
When a request is aborted (either by timeout or manual cancellation), the fetch API throws a DOMException:
try {
const response = await resilientFetch ( '/api/data' );
return await response . json ();
} catch ( error ) {
if ( error . name === 'AbortError' ) {
console . log ( 'Request was cancelled' );
} else if ( error . message === 'TimeoutError' ) {
console . log ( 'Operation timed out' );
} else {
console . log ( 'Other error:' , error );
}
}
Comparison with Native Fetch
Feature Native Fetch resilientFetch Basic usage ✅ ✅ All fetch options ✅ ✅ Manual abort signal ✅ ✅ Auto abort signal ❌ ✅ Timeout integration Manual Automatic Works outside resilient context ✅ ✅ (fallback)
Use Cases
API Clients : Build resilient HTTP clients with automatic timeout
Data Fetching : Fetch data with automatic cancellation on timeout
File Operations : Upload/download with timeout protection
Polling : Poll endpoints with automatic timeout
GraphQL : Query GraphQL APIs with resilience
Webhooks : Call webhooks with timeout and retry
Integration with Active Signal Context
The resilientFetch function leverages the same active signal mechanism as sleep:
When withResilience enables useAbortSignal, it creates an AbortController
The signal is set as the active signal for the execution context
resilientFetch automatically uses this active signal
When timeout occurs, all pending fetch requests are cancelled
The active signal is restored after execution
This eliminates the need to manually thread abort signals through your code - they’re automatically available to all resilient utilities.