原生 JavaScript Fetch API 异步请求与错误捕获标准封装

在日常做小型 Web 页面或纯静态单页应用时,单独为了几个 HTTP 请求引入庞大的 Axios 往往显得过于笨重。现代浏览器的 fetch 已经非常成熟,但原生的 fetch 在遇到 HTTP 404 或 500 状态码时并不会自动触发 Promise reject,且原生不带超时取消机制。本文给出一个轻巧实用的原生封装。

一、核心封装代码(带超时与异常处理)

/**
 * 现代轻量级 Fetch 封装
 * @param {string} url 请求地址
 * @param {object} options fetch 参数配置
 * @param {number} timeout 超时时间 (毫秒),默认 8000ms
 */
async function request(url, options = {}, timeout = 8000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);

  const defaultHeaders = {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  };

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
      headers: {
        ...defaultHeaders,
        ...options.headers
      }
    });

    // 重点:显式判断 HTTP 状态码
    if (!response.ok) {
      throw new Error(`HTTP 异常 [状态码: ${response.status}]`);
    }

    // 自动根据 Content-Type 解析响应体
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return await response.json();
    }
    return await response.text();

  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error(`请求超时,超过了 ${timeout}ms 限制`);
    }
    throw error;
  } finally {
    clearTimeout(timer);
  }
}

二、日常调用示例

// 1. GET 请求
async function loadUserData() {
  try {
    const data = await request('https://api.example.com/user/profile');
    console.log('获取数据成功:', data);
  } catch (err) {
    console.error('加载失败:', err.message);
  }
}

// 2. POST 提交 JSON 数据
async function updateSettings(payload) {
  try {
    const res = await request('https://api.example.com/settings', {
      method: 'POST',
      body: JSON.stringify(payload)
    }, 5000);
    alert('保存成功');
  } catch (err) {
    alert(`操作失败: ${err.message}`);
  }
}

三、为什么不要对 404 掉以轻心?

原生 fetch 的设计哲学是:只要服务器有网络层面的响应(哪怕返回 404 Not Found 或 502 Bad Gateway),它都认为“网络通讯成功”并进入 resolve 状态。只有在断网、DNS 解析失败或跨域被拦截时才会 reject。因此上述封装中的 if (!response.ok) 拦截是稳健业务必不可少的一环。