TypeScript HTTP 客户端:从 A 到 Z 正确发起请求

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

TypeScript HTTP 客户端:从 A 到 Z 正确发起请求

\n\n

现代 Web 开发离不开与远程服务器的交互。无论是从 REST API 获取数据、提交表单还是上传文件,HTTP 请求都是客户端-服务器架构的基础。TypeScript 凭借其严格的类型化和强大的工具,不仅能让 HTTP 请求的处理更安全,还能使其更易于维护。在本文中,我们将探讨如何创建一个得体的 TypeScript HTTP 客户端,避免常见错误,并编写可预测运行的代码。

\n\n

1. 基础:fetch API 及其类型化

\n\n

浏览器内置的 fetch 是一个用于执行 HTTP 请求的现代且灵活的工具。然而,在 TypeScript 中使用它需要对响应进行正确的类型化。默认情况下,fetch 返回 Promise<Response>,我们需要明确指定期望接收的数据类型。

\n\n
interface User {\n  id: number;\n  name: string;\n  email: string;\n}\n\nasync function getUser(id: number): Promise<User> {\n  const response = await fetch(`https://api.example.com/users/${id}`);\n  \n  if (!response.ok) {\n    throw new Error(`HTTP error! status: ${response.status}`);\n  }\n  \n  const data: User = await response.json();\n  return data;\n}\n
\n\n

请注意对 response.ok 的检查。这一点至关重要:即使服务器返回了错误(如 404、500 等),fetch 也不会自动抛出异常。我们必须手动处理状态码。

\n\n

对于包含请求体的 POST 请求,需要指定 Content-Type 标头:

\n\n
async function createUser(userData: Omit<User, 'id'>): Promise<User> {\n  const response = await fetch('https://api.example.com/users', {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify(userData),\n  });\n\n  if (!response.ok) {\n    throw new Error(`Failed to create user: ${response.statusText}`);\n  }\n\n  return response.json();\n}\n
\n\n

2. 创建通用 HTTP 客户端

\n\n

与其每次都从头编写 fetch,不如创建一个封装——一个通用的 HTTP 客户端。这样可以集中管理基础 URL、标头和错误处理。我们来看一个基于类的示例:

\n\n
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\ninterface RequestConfig<TBody = unknown> {\n  method: HttpMethod;\n  url: string;\n  body?: TBody;\n  headers?: Record<string, string>;\n  params?: Record<string, string>;\n}\n\nclass HttpClient {\n  private baseUrl: string;\n  private defaultHeaders: Record<string, string>;\n\n  constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {\n    this.baseUrl = baseUrl;\n    this.defaultHeaders = {\n      'Content-Type': 'application/json',\n      ...defaultHeaders,\n    };\n  }\n\n  private async request<TResponse, TBody = unknown>(\n    config: RequestConfig<TBody>\n  ): Promise<TResponse> {\n    const url = new URL(`${this.baseUrl}${config.url}`);\n    \n    if (config.params) {\n      Object.entries(config.params).forEach(([key, value]) => {\n        url.searchParams.append(key, value);\n      });\n    }\n\n    const response = await fetch(url.toString(), {\n      method: config.method,\n      headers: { ...this.defaultHeaders, ...config.headers },\n      body: config.body ? JSON.stringify(config.body) : undefined,\n    });\n\n    if (!response.ok) {\n      const errorBody = await response.text();\n      throw new Error(`Request failed: ${response.status} ${response.statusText}. Body: ${errorBody}`);\n    }\n\n    // 对于 DELETE 或其他可能不返回响应体的方法\n    if (response.status === 204) {\n      return undefined as TResponse;\n    }\n\n    return response.jso                

博客

书籍推荐