Skip to content

Instantly share code, notes, and snippets.

@image72
Created October 9, 2024 19:08
Show Gist options
  • Save image72/e23ddcf695e634e19a5db2787935ab8c to your computer and use it in GitHub Desktop.
Save image72/e23ddcf695e634e19a5db2787935ab8c to your computer and use it in GitHub Desktop.
type RequestConfig = RequestInit & {
baseURL?: string;
timeout?: number;
params?: Record<string, string>;
signal?: AbortSignal;
};
type InterceptorFn = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
class FetchWrapper {
private baseURL: string;
private requestInterceptors: InterceptorFn[] = [];
private responseInterceptors: ((response: Response) => Response | Promise<Response>)[] = [];
constructor(baseURL: string = '') {
this.baseURL = baseURL;
}
private async applyInterceptors(config: RequestConfig): Promise<RequestConfig> {
for (const interceptor of this.requestInterceptors) {
config = await interceptor(config);
}
return config;
}
private async applyResponseInterceptors(response: Response): Promise<Response> {
for (const interceptor of this.responseInterceptors) {
response = await interceptor(response);
}
return response;
}
async request<T>(url: string, config: RequestConfig = {}): Promise<T> {
config = await this.applyInterceptors(config);
const signals: AbortSignal[] = [];
const timeout = config.timeout || 0;
if (timeout > 0) {
signals.push(AbortSignal.timeout(timeout));
}
if (config.signal) {
signals.push(config.signal);
}
const signal = signals.length > 0 ? AbortSignal.any(signals) : undefined;
const fullURL = new URL(url, this.baseURL);
if (config.params) {
Object.entries(config.params).forEach(([key, value]) => {
fullURL.searchParams.append(key, value);
});
}
try {
const response = await fetch(fullURL.toString(), { ...config, signal });
const interceptedResponse = await this.applyResponseInterceptors(response);
if (!interceptedResponse.ok) {
throw new Error(`HTTP error! status: ${interceptedResponse.status}`);
}
return await interceptedResponse.json();
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
if (signal?.reason instanceof DOMException && signal.reason.name === 'TimeoutError') {
throw new Error('Request timed out');
} else {
throw new Error('Request aborted');
}
}
throw error;
}
}
get<T>(url: string, config: RequestConfig = {}): Promise<T> {
return this.request<T>(url, { ...config, method: 'GET' });
}
post<T>(url: string, data?: any, config: RequestConfig = {}): Promise<T> {
return this.request<T>(url, {
...config,
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
...config.headers,
},
});
}
// Add other methods (put, delete, etc.) as needed
interceptors = {
request: {
use: (fn: InterceptorFn) => {
this.requestInterceptors.push(fn);
},
},
response: {
use: (fn: (response: Response) => Response | Promise<Response>) => {
this.responseInterceptors.push(fn);
},
},
};
}
export default FetchWrapper;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment