TypeScript HTTP 客户端:正确的请求与响应处理

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

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

开始课程

引言:为什么正确的HTTP客户端是现代Web应用的基础

任何现代Web应用都通过HTTP与服务器交互。无论是加载商品列表、提交注册表单还是获取仪表盘数据——这一切都归结为发送HTTP请求。在TypeScript中,凭借其强大的类型系统,我们可以构建一个不仅可用,而且真正可靠且可预测的HTTP客户端。

在本文中,我们将探讨如何创建一个TypeScript HTTP客户端,该客户端能够处理错误、正确地对响应进行类型化、管理标头并支持中间件。您将了解到为什么使用原始的fetch而不进行封装是一种不良实践,以及如何将其转变为强大的工具。

基础:从原始fetch到类型化客户端

让我们从一个基本示例开始。JavaScript内置的fetch是一个很好的起点,但它不提供方便的类型化和错误处理。在TypeScript中,我们可以将其封装到自己的函数中。

简单的fetch封装

创建一个基础类HttpClient,它将接收基础URL和设置。

interface RequestOptions {
  headers?: Record<string, string>;
  params?: Record<string, string>;
}

class HttpClient {
  private baseUrl: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = defaultHeaders;
  }

  private buildUrl(path: string, params?: Record<string, string>): string {
    const url = new URL(`${this.baseUrl}${path}`);
    if (params) {
      Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value));
    }
    return url.toString();
  }

  async get<T>(path: string, options?: RequestOptions): Promise<T> {
    const url = this.buildUrl(path, options?.params);
    const response = await fetch(url, {
      method: 'GET',
      headers: { ...this.defaultHeaders, ...options?.headers },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json() as Promise<T>;
  }
}

现在调用client.get<User>('/users/1')将返回一个类型化的User对象。这比使用any要好得多。

高级错误处理和响应类型化

简单地抛出错误并不是最佳方法。我们需要区分网络错误、服务器错误(4xx、5xx)和JSON解析错误。让我们创建自己的错误类。

创建错误类

class HttpError extends Error {
  status: number;
  body: any;

  constructor(message: string, status: number, body?: any) {
    super(message);
    this.name = 'HttpError';
    this.status = status;
    this.body = body;
  }
}

class NetworkError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ParseError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'ParseError';
  }
}

在get方法中改进错误处理

async get<T>(path: string, options?: RequestOptions): Promise<T> {
  try {
    const url = this.buildUrl(path, options?.params);
    const response = await fetch(url, {
      method: 'GET',
      headers: { ...this.defaultHeaders, ...options?.headers },
    });

    let body: any;
    try {
      body = await response.json();
    } catch {
      throw new ParseError('Failed to parse response as JSON');
    }

    if (!response.ok) {
      throw new HttpError(
        `Request failed with status ${response.status}`,
        response.status,
        body
      );
    }

    return body as T;
  } catch (er                

博客

书籍推荐