你真的会用 Fetch 吗?TypeScript 环境下必须掌握的7种请求模式

第一章:Fetch API 的核心机制与 TypeScript 集成

Fetch API 是现代浏览器提供的用于发起网络请求的标准接口,它基于 Promise 提供了更简洁、灵活的方式处理 HTTP 请求与响应。结合 TypeScript 的静态类型系统,开发者可以在编译期捕获潜在错误,提升代码的可维护性与健壮性。

使用 Fetch 发起基本请求

通过 fetch() 可以轻松获取远程资源。以下示例展示了如何使用 TypeScript 定义响应数据结构并发起 GET 请求:

// 定义用户数据类型
interface User {
  id: number;
  name: string;
  email: string;
}

// 获取用户列表
async function fetchUsers(): Promise<User[]> {
  const response = await fetch('/api/users');
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const users: User[] = await response.json();
  return users;
}
上述代码中,TypeScript 确保了解析后的 JSON 数据符合预期结构,增强了类型安全性。

配置请求选项

Fetch 支持通过第二个参数传入配置项,例如设置请求方法、头部和请求体:
  • 使用 method 指定 HTTP 方法(如 POST、PUT)
  • 通过 headers 设置内容类型和其他认证信息
  • 使用 body 发送序列化数据

const response = await fetch('/api/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ username: 'admin', password: '123456' }),
});

错误处理与类型守卫

由于网络请求具有不确定性,建议封装统一的错误处理逻辑,并利用 TypeScript 的类型守卫确保数据完整性。
特性描述
Promise 支持原生支持异步操作,避免回调地狱
Type SafetyTypeScript 提供接口类型校验,减少运行时错误
可扩展性易于封装为通用请求函数或集成至服务类

第二章:基础请求模式的类型安全实现

2.1 理解 Fetch 的底层工作原理与 Promise 类型流

Fetch API 基于现代浏览器的网络请求机制,其底层依赖于 `Promise` 异步模型实现资源获取。它返回一个可解析为 `Response` 对象的 Promise,该对象封装了 HTTP 响应的元数据和响应体流。
Promise 驱动的异步流程
Fetch 请求一旦发起,立即返回 Promise,避免阻塞主线程。响应通过 `.then()` 链式处理,实现非阻塞的数据流控制。

fetch('/api/data')
  .then(response => {
    if (!response.ok) throw new Error('Network error');
    return response.json(); // 返回新的 Promise
  })
  .then(data => console.log(data));
上述代码中,`fetch()` 返回 Promise,`.json()` 方法也返回 Promise,形成链式流处理。这是因为响应体是可读流(ReadableStream),需异步消费。
流与数据消费机制
Response 主体通过 `body` 暴露为 `ReadableStream`,支持分块读取。使用 `response.text()`、`json()` 等方法后,流将关闭,不可重复读取。
  • 每次 fetch 只能消费一次响应体
  • 流式处理适用于大文件或实时数据
  • Promise 链确保异步操作有序执行

2.2 GET 请求封装:泛型响应类型的定义与校验

在构建类型安全的 HTTP 客户端时,GET 请求的泛型封装能显著提升代码复用性与可维护性。通过引入泛型参数,可将响应结构与业务数据解耦。
泛型响应结构设计
定义统一的响应体接口,约束所有 API 返回格式:

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}
该设计允许调用方明确知晓 data 字段的精确类型,如 ApiResponse<User[]> 表示用户列表响应。
运行时类型校验
结合 zod 实现响应数据校验,防止非法数据流入:

const UserSchema = z.object({
  id: z.number(),
  name: z.string()
});
请求返回后自动验证 JSON 结构,确保类型安全贯穿编译与运行时。

2.3 POST 请求构建:请求体序列化与 Content-Type 控制

在构建 POST 请求时,正确设置请求体格式与对应的 Content-Type 是确保服务端正确解析数据的关键。
常见 Content-Type 类型
  • application/json:用于传输 JSON 数据,现代 API 最常用
  • application/x-www-form-urlencoded:表单提交的传统格式
  • multipart/form-data:文件上传等复杂数据场景
JSON 请求示例
fetch('/api/user', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Alice', age: 30 })
});
该代码发送一个 JSON 格式的 POST 请求。Content-Type 明确指定为 application/jsonbody 使用 JSON.stringify 将对象序列化为字符串,符合 RESTful API 的通用规范。

2.4 错误处理机制:状态码判断与 reject 类型精确推断

在异步编程中,精确的错误类型推断能显著提升代码的可维护性。现代 TypeScript 结合 Promise 的 reject 机制,可通过条件类型实现异常分支的类型安全。
状态码驱动的错误分类
根据 HTTP 状态码区分客户端与服务端错误,有助于精细化处理:

interface ApiError {
  status: number;
  message: string;
}

const request = async (): Promise<string> => {
  const res = await fetch("/api/data");
  if (!res.ok) {
    // 拒绝时携带结构化错误
    throw { status: res.status, message: res.statusText } as ApiError;
  }
  return await res.text();
};
该模式将网络响应状态映射为具体错误对象,便于后续捕获和判断。
利用泛型实现 reject 类型推断
TypeScript 能基于 throw 值自动推导 Promise 的 rejected 类型:
  • 当 throw 非空值时,Promise 的 reject 类型被精确收敛;
  • 结合 await 使用,可在 try/catch 中获得完整的类型提示。

2.5 超时控制:AbortController 与 Promise.race 类型建模

在异步编程中,超时控制是保障系统健壮性的关键环节。JavaScript 提供了多种机制来实现请求或任务的超时管理,其中 `AbortController` 与 `Promise.race` 的组合尤为高效。
AbortController 中断机制
`AbortController` 允许开发者主动中断未完成的异步操作,常用于 `fetch` 请求:
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch('/api/data', { signal: controller.signal })
  .then(response => response.json())
  .catch(err => {
    if (err.name === 'AbortError') console.log('请求已超时');
  });
该代码设置 5 秒超时,触发后自动终止请求,避免资源浪费。
结合 Promise.race 实现类型建模
利用 `Promise.race` 可构建更清晰的超时逻辑:
const timeout = ms => new Promise((_, reject) =>
  setTimeout(() => reject(new Error('Request timeout')), ms)
);

Promise.race([
  fetch('/api/data'),
  timeout(5000)
]).catch(console.error);
`Promise.race` 会以最先完成(或拒绝)的 Promise 决定结果,从而实现声明式超时控制,提升代码可读性与可维护性。

第三章:认证与头部管理的最佳实践

3.1 持久化认证头:自定义客户端类与拦截器设计

在构建高可用的API客户端时,持久化认证信息是保障请求连续性的关键。通过封装自定义HTTP客户端,可统一管理认证头的注入与刷新。
拦截器设计模式
使用拦截器可在请求发出前自动附加认证头,避免重复代码。常见于OAuth2或JWT场景。
  • 请求拦截:注入Authorization头
  • 响应拦截:检测401状态并触发令牌刷新
  • 链式处理:支持多个中间件逻辑叠加
type AuthInterceptor struct {
    Token string
}

func (a *AuthInterceptor) RoundTrip(req *http.Request) (*http.Response, error) {
    req.Header.Set("Authorization", "Bearer "+a.Token)
    return http.DefaultTransport.RoundTrip(req)
}
上述代码实现了一个基础的RoundTripper拦截器,将持久化的Token注入每个请求头中。Token字段可由外部安全存储注入,并支持运行时动态更新,确保多协程环境下的安全性。

3.2 动态 Header 注入:联合类型与可选字段的安全处理

在构建高可靠性的网关中间件时,动态注入 HTTP 头部需应对字段缺失与类型歧义问题。通过引入联合类型(Union Types)和可选链操作,可有效规避运行时异常。

类型安全的 Header 处理策略

使用 TypeScript 的联合类型明确 header 值可能为 stringstring[]undefined,结合可选属性语法确保访问安全:

type SafeHeaders = {
  [key: string]: string | string[] | undefined;
};

function injectHeader(
  headers: SafeHeaders, 
  key: string, 
  value?: string | string[]
): void {
  if (value !== undefined) {
    headers[key] = value;
  }
}
上述代码中,SafeHeaders 明确定义了 header 字段的多种合法形态,value? 参数使用可选修饰符避免未定义传参导致的覆盖错误。

运行时类型校验流程

输入参数 → 类型判断 → 非空检查 → 格式标准化 → 写入 headers

3.3 Token 刷新机制:链式请求与状态同步类型建模

在现代前后端分离架构中,Token 刷新机制需兼顾安全性与用户体验。当访问 Token(Access Token)过期时,系统应透明地使用刷新 Token(Refresh Token)获取新令牌,避免用户频繁重新登录。
链式请求的实现逻辑
通过拦截器统一处理 401 响应,触发刷新流程并重试原始请求:

axios.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      const newToken = await refreshToken();
      setAuthToken(newToken);
      return axios(originalRequest);
    }
    return Promise.reject(error);
  }
);
上述代码通过标记 _retry 防止循环重试,确保链式调用的稳定性。
状态同步的类型建模
使用 TypeScript 建模认证状态机,明确各状态间迁移规则:
当前状态触发事件下一状态
AuthenticatedToken ExpiredRefreshing
RefreshingRefresh SuccessAuthenticated
RefreshingRefresh FailLoggedOut
该模型保障多请求并发时的状态一致性,防止重复刷新。

第四章:高级请求模式的工程化封装

4.1 并发请求控制:Promise.all 类型聚合与错误降级策略

在前端并发场景中,Promise.all 常用于并行发起多个请求并等待全部完成。然而,其“全量失败”特性意味着任一 Promise 拒绝将导致整体异常,影响可用性。
错误降级的必要性
为提升系统韧性,需对异常请求进行隔离处理。通过 Promise.allSettled 可确保所有请求执行完毕,无论成功或失败:

Promise.allSettled([
  fetch('/api/user'),
  fetch('/api/config'),
  fetch('/api/feature-flags')
]).then(results => {
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`请求 ${index} 成功:`, result.value);
    } else {
      console.warn(`请求 ${index} 失败:`, result.reason);
    }
  });
});
该方法返回一个对象数组,包含每个请求的最终状态,便于分别处理结果与错误,实现数据降级渲染。
策略对比
方法失败行为适用场景
Promise.all任一失败即拒绝强依赖所有结果
Promise.allSettled等待全部完成弱依赖、可降级

4.2 文件上传进度监控:FormData 构造与 onprogress 类型补全

在实现文件上传时,FormData 是构建多部分表单数据的核心工具。通过将文件添加到 FormData 实例中,可轻松适配 XMLHttpRequest 或 Fetch API 的传输需求。
构造 FormData 并绑定上传请求
const formData = new FormData();
formData.append('file', fileInput.files[0]);

const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.send(formData);
上述代码创建一个 FormData 对象并追加用户选择的文件,随后通过 XMLHttpRequest 发送至服务端。
监听上传进度:onprogress 事件类型补全
为实现进度监控,需监听 xhr.upload.onprogress 事件:
xhr.upload.onprogress = function(e) {
  if (e.lengthComputable) {
    const percent = (e.loaded / e.total) * 100;
    console.log(`上传进度: ${percent.toFixed(2)}%`);
  }
};
其中,e.loaded 表示已上传字节数,e.total 为总字节数,两者结合可计算实时进度百分比。该事件属于 ProgressEvent 类型,在 TypeScript 中需正确声明参数类型以避免类型检查错误。

4.3 缓存策略实现:Request Cache 选项与 ETag 协商类型定义

在现代 Web 架构中,合理利用缓存机制可显著降低服务器负载并提升响应速度。`Request Cache` 提供多种缓存行为选项,如 `no-store`、`no-cache`、`force-cache` 等,用于控制请求过程中是否使用本地缓存或强制发起网络请求。
ETag 与条件请求协商
ETag(实体标签)是资源特定版本的唯一标识符,服务器通过 `ETag` 响应头返回,客户端在后续请求中通过 `If-None-Match` 携带该值进行比对,实现高效的内容变更检测。
GET /api/data HTTP/1.1
Host: example.com
If-None-Match: "abc123"

HTTP/1.1 304 Not Modified
ETag: "abc123"
上述流程表明,当资源未变更时,服务器返回 304,避免重复传输数据。
常见 Request Cache 选项语义
选项行为说明
default遵循标准 HTTP 缓存规则
no-cache始终验证缓存新鲜度
force-cache忽略过期策略,直接使用缓存

4.4 重试机制设计:指数退避算法与泛型重试函数签名

在分布式系统中,网络波动和临时性故障频繁发生,合理的重试机制能显著提升系统的健壮性。指数退避算法通过逐步延长重试间隔,避免服务雪崩。
指数退避算法原理
每次失败后等待时间为基准延迟乘以 2 的指数增长,例如:1s、2s、4s、8s,最大不超过上限值。
func retryWithExponentialBackoff(operation func() error, maxRetries int) error {
    var err error
    for i := 0; i < maxRetries; i++ {
        if err = operation(); err == nil {
            return nil
        }
        time.Sleep(time.Second * time.Duration(1<
该函数尝试执行操作,失败则按 2^i 秒延迟重试,最多 maxRetries 次。
泛型重试函数设计
使用 Go 泛型可统一处理不同返回类型的操作:
func Retry[T any](fn func() (T, error), maxRetries int) (T, error) {
    var result T
    for i := 0; i <= maxRetries; i++ {
        result, err := fn()
        if err == nil {
            return result, nil
        }
        time.Sleep(time.Second * time.Duration(1<
此泛型函数支持任意返回类型 T,提升代码复用性和类型安全性。

第五章:从 Fetch 到企业级 HTTP 客户端的演进思考

在现代前端架构中,原生 fetch 虽然提供了基础的网络请求能力,但在大型系统中逐渐暴露出维护性差、错误处理冗余等问题。企业级应用更倾向于封装统一的 HTTP 客户端,以实现拦截器、超时控制、重试机制和认证集成。
统一请求层的设计实践
通过封装 Axios 或基于 fetch 构建自定义客户端,可集中处理鉴权、日志与异常。例如:
class APIClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.defaultOptions = { headers: { 'Content-Type': 'application/json' } };
  }

  async request({ method, url, data }) {
    const config = {
      method,
      ...this.defaultOptions,
      body: data ? JSON.stringify(data) : undefined
    };

    const response = await fetch(this.baseURL + url, config);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }
}
功能增强的关键策略
  • 请求拦截:自动注入 JWT 令牌
  • 响应缓存:利用 Redis 或内存缓存减少重复请求
  • 超时熔断:设置 10s 超时并触发降级逻辑
  • 重试机制:对 5xx 错误自动重试 2 次
性能与可观测性对比
特性原生 Fetch企业级客户端
错误统一处理需手动实现内置中间件支持
请求追踪集成 Sentry 日志埋点
[发起请求] → [认证拦截] → [序列化] → [发送] ↖ ← [重试逻辑] ← [失败] ← ↙
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值