高级 TypeScript HTTP 客户端:从基础请求到工业级解决方案
在开发现代 TypeScript Web 应用程序时,HTTP 请求处理远不止简单的 fetch 调用。在高级层面,不仅需要发送请求,还需要妥善处理错误、管理超时、对响应进行类型化、引入拦截器,甚至缓存数据。在本文中,我们将探讨如何构建一个可靠且灵活的 TypeScript HTTP 客户端,适用于生产级项目。
1. 使用泛型创建类型化 HTTP 客户端
TypeScript 的主要优势之一是强类型化。让我们创建一个基础类 HttpClient,它通过泛型接受响应类型。这有助于在编译阶段避免错误。
// types.tsexport interface ApiResponse<T> { data: T; status: number; ok: boolean;}
export interface RequestConfig { headers?: Record<string, string>; params?: Record<string, string>; signal?: AbortSignal;}
// http-client.tsimport { ApiResponse, RequestConfig } from './types';
class HttpClient { private baseUrl: string;
constructor(baseUrl: string) { this.baseUrl = baseUrl; }
async get<T>(endpoint: string, config?: RequestConfig): Promise<ApiResponse<T>> { const url = new URL(`${this.baseUrl}${endpoint}`); if (config?.params) { Object.entries(config.params).forEach(([key, value]) => { url.searchParams.append(key, value); }); }
const response = await fetch(url.toString(), { method: 'GET', headers: { 'Content-Type': 'application/json', ...config?.headers, }, signal: config?.signal, });
const data: T = await response.json(); return { data, status: response.status, ok: response.ok, }; }
async post<T, U>(endpoint: string, body: U, config?: RequestConfig): Promise<ApiResponse<T>> { const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...config?.headers, }, body: JSON.stringify(body), signal: config?.signal, });
const data: T = await response.json(); return { data, status: response.status, ok: response.ok }; }}
export default HttpClient;2. 错误处理和超时
高级客户端应正确处理网络错误和业务逻辑错误。让我们使用 AbortController 添加超时机制,并进行自定义错误处理。
// errors.tsexport class HttpError extends Error { status: number; data?: unknown;
constructor(message: string, status: number, data?: unknown) { super(message); this.name = 'HttpError'; this.status = status; this.data = data; }}
export class TimeoutError extends Error { constructor(timeout: number) { super(`请求在 ${timeout}ms 后超时`); this.name = 'TimeoutError'; }}
// http-client.ts (补充)async requestWithTimeout<T>( endpoint: string, options: RequestInit, timeout: number = 5000): Promise<ApiResponse<T>> { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout);
try { const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, signal: controller.signal, });
if (!response.ok) { const errorData = await response.json().catch(() => null); throw new HttpError( `HTTP ${response.status}: ${response.statusText}`, response.status, errorData ); }
const data: T = await response.json(); r