用JavaScript fetch API发送异步请求的代码模板

作者:半夏

时间:2026-11-03

浏览:667

提供结构规范、开箱即用的 JavaScript fetch API 异步请求代码模板。涵盖基础 GET、JSON POST、响应状态判定、超时中断与工程化封装,帮助开发者快速掌握安全健壮的前端网络请求写法。

文章目录

在现代 Web 开发中,原生 fetch API 已经全面取代传统的 XMLHttpRequest,成为浏览器端发送 HTTP 异步请求的标准方式。虽然 fetch 的语法直观简洁,但由于其内部对 HTTP 错误状态码的特殊处理机制,很多初学者在编写请求代码时容易漏掉状态校验、超时保护与响应流控制。本文直接提供标准化、经过工程验证的 fetch 异步请求代码模板,涵盖基础 GET、数据 POST、网络及业务异常双层拦截,以及通用的模块化封装函数。

用JavaScript fetch API发送异步请求的代码模板

一、最简 GET 请求代码模板:标准 async/await 结构

GET 请求用于从服务器获取数据,也是最常用的请求方式。在 fetch 中,默认的请求方法就是 GET,因此在没有额外配置的情况下可以省略 method: 'GET'。但在生产实践中,必须通过 try...catch 结合 async/await 构建可控的异常捕获链路。

以下是用于获取 JSON 数据的标准 GET 请求模板:

/**
 * 发送 GET 请求的标准异步模板
 * @param {string} url 请求的目标地址
 * @param {Record<string, any>} [params] 可选的查询参数对象
 * @returns {Promise<any>} 解析后的 JSON 数据
 */
async function fetchGetData(url, params = {}) {
  try {
    // 1. 如果存在查询参数,使用 URLSearchParams 格式化并拼接到 URL
    const targetUrl = new URL(url, window.location.origin);
    Object.keys(params).forEach(key => {
      if (params[key] !== undefined && params[key] !== null) {
        targetUrl.searchParams.append(key, params[key]);
      }
    });

    // 2. 发起网络请求
    const response = await fetch(targetUrl.toString(), {
      method: 'GET',
      headers: {
        'Accept': 'application/json'
      }
    });

    // 3. 校验 HTTP 响应状态码是否在 200~299 区间
    if (!response.ok) {
      throw new Error(`HTTP 状态异常: ${response.status} ${response.statusText}`);
    }

    // 4. 将响应流解析为 JSON 对象
    const result = await response.json();
    return result;

  } catch (error) {
    // 5. 捕获网络异常、DNS 解析失败或主动抛出的 HTTP 错误
    console.error('GET 请求失败:', error.message);
    throw error;
  }
}

// 调用示例:
// fetchGetData('/api/articles', { category: 'javascript', page: 1 })
//   .then(data => console.log('文章列表:', data))
//   .catch(err => console.error('业务捕获到错误:', err));
注意事项:GET 与 HEAD 请求规范中明确禁止携带 body。如果在调用 fetch 时传入包含 body 的 GET 配置,现代浏览器会直接抛出 TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body 异常。
用JavaScript fetch API发送异步请求的代码模板
图 1:fetch 异步请求的完整执行生命周期与关键步骤

二、提交数据:POST 请求配置模板与 Headers 设定

向服务端提交数据时,最常用的是通过 POST 传递 JSON 格式载荷。与 GET 请求相比,POST 模板的核心在于两点:一是必须在 headers 中指明 'Content-Type': 'application/json',否则服务端可能将其当做纯文本或二进制流而无法正常反序列化;二是在 body 中使用 JSON.stringify() 将 JavaScript 对象序列化为字符串。

/**
 * 发送 POST 请求提交 JSON 数据的标准模板
 * @param {string} url 目标地址
 * @param {object} payload 待发送的业务数据对象
 * @returns {Promise<any>} 服务端返回的数据
 */
async function fetchPostJson(url, payload = {}) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(payload)
    });

    // 判定服务端是否成功返回 2xx 状态码
    if (!response.ok) {
      // 尝试读取服务端返回的具体错误说明(如验证未通过的字段提示)
      let errorMessage = `请求失败: ${response.status}`;
      try {
        const errorBody = await response.json();
        errorMessage = errorBody.message || errorMessage;
      } catch (_) {
        // 如果返回的不是 JSON,退回默认状态文本
        errorMessage = response.statusText || errorMessage;
      }
      throw new Error(errorMessage);
    }

    return await response.json();

  } catch (error) {
    console.error('POST 请求提交失败:', error);
    throw error;
  }
}

// 调用示例:
// fetchPostJson('/api/users/profile', { nickname: '张三', role: 'admin' })
//   .then(res => console.log('保存成功:', res))
//   .catch(err => alert('保存失败: ' + err.message));

拓展场景:发送 FormData 表单数据

如果提交的内容包含文件上传(如 <input type="file">)或标准表单对象,请直接使用浏览器原生的 FormData 对象。切记此时不要手动设置 Content-Type,浏览器会自动添加形如 multipart/form-data; boundary=----WebKitFormBoundary... 的分界标识符,若手动设置反而会丢失 boundary 导致服务端解析失败。

async function fetchPostFormData(url, formData) {
  const response = await fetch(url, {
    method: 'POST',
    // 关键:不要配置 'Content-Type',让浏览器自动计算 Boundary
    body: formData
  });
  if (!response.ok) throw new Error(`上传失败: ${response.status}`);
  return await response.json();
}

三、核心陷阱排查:为什么 fetch 不会自动 reject HTTP 错误?

这是从 axios 或传统请求库切换到原生 fetch 时最容易踩坑的技术细节:当服务器返回 404 Not Found、500 Internal Server Error 或 403 Forbidden 等状态码时,fetch() 返回的 Promise 并不会被 reject,而是会正常 resolve!

在 fetch 的设计规范中,只有当遇到网络层致命故障(例如用户断网、DNS 查询失败、网络连接被重置,或者触发跨域 CORS 拦截策略)导致根本无法完成 HTTP 会话时,Promise 才会进入 reject 状态并跳入 catch 块。

用JavaScript fetch API发送异步请求的代码模板
图 2:fetch 的网络致命异常与 HTTP 错误状态码机制对比
防范规则:每一次调用 fetch 获取到 response 对象后,执行业务逻辑之前,必须首先检查 response.ok。该布尔值属性在状态码位于 200 ~ 299 时为 true,其余均为 false。只有在 !response.ok 时手动执行 throw new Error(...),才能让错误顺利流转到统一的 catch 异常处理块中。
响应表现 / 状态码 Promise 状态 是否进入 catch 块 推荐处理方式
200 OK / 201 Created Resolved 否 response.ok === true,调用 response.json() 消费数据
400 / 401 / 404 / 500 Resolved(注意!) 否(若不手动 throw) 检查 if (!response.ok),主动提取服务端错误体并 throw
网络离线 / DNS 解析失败 Rejected 是 跳入 catch (err),类型为 TypeError: Failed to fetch
请求超时 / 主动 Abort Rejected 是 跳入 catch (err),名称为 AbortError 或 TimeoutError

四、超时控制与请求中断:基于 AbortController 的健壮模板

原生 fetch API 本身并不像传统的 XHR 那样提供 timeout 配置项。如果后端接口卡死或处于弱网慢速连接,未配置超时的 fetch 会一直处于挂起状态,耗尽浏览器并发连接池资源并严重影响用户体验。

现代标准中,通过原生的 AbortController(或更高版本直接支持的 AbortSignal.timeout(ms))可以为请求精准配置超时熔断与主动取消机制。

/**
 * 带有超时控制的 fetch 请求模板
 * @param {string} url 请求地址
 * @param {RequestInit} [options] 配置选项
 * @param {number} [timeoutMs] 超时毫秒数,默认 8000ms
 */
async function fetchWithTimeout(url, options = {}, timeoutMs = 8000) {
  // 1. 初始化中断控制器
  const controller = new AbortController();
  const { signal } = controller;

  // 2. 设置定时器:达到超时时间后触发中断
  const timeoutId = setTimeout(() => {
    controller.abort(new Error(`请求超时,超过了设定的 ${timeoutMs}ms 限制`));
  }, timeoutMs);

  try {
    // 3. 将 signal 信号传递给 fetch options
    const response = await fetch(url, {
      ...options,
      signal
    });

    // 4. 及时清除超时定时器,防止成功返回后仍然触发 abort
    clearTimeout(timeoutId);

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

    return await response.json();

  } catch (error) {
    clearTimeout(timeoutId);

    // 5. 识别是否由 AbortController 中断引起
    if (error.name === 'AbortError') {
      console.warn('请求已由控制器主动取消或已触发超时');
    } else {
      console.error('网络或业务异常:', error.message);
    }
    throw error;
  }
}
现代浏览器语法糖:在 Node.js 18+ 及现代主流浏览器(Chrome 103+、Safari 16+、Firefox 100+)中,你可以直接使用单行原生语法糖:signal: AbortSignal.timeout(8000),无需手动声明 setTimeout 与 clearTimeout。但在需要向下兼容或在组件卸载时手动触发取消的场景下,上述 new AbortController() 模板依旧是最稳妥通用的方案。

五、生产级通用封装:开箱即用的 request 请求函数模板

在实际项目开发中,如果每个接口都手写一遍 fetch、Headers 拼接、状态检查和超时定时器,代码将充斥大量冗余。下面提供一套适用于中小型工程的轻量级通用请求客户端模板。该模板支持自动拼装 Base URL、自动添加鉴权 Token、统一序列化、超时防护与标准化错误抛出。

用JavaScript fetch API发送异步请求的代码模板
图 3:通用 fetch 请求客户端的多层分工与处理管线架构
/**
 * 工业级通用异步请求封装模板 (http-client.js)
 */
const DEFAULT_CONFIG = {
  baseURL: 'https://api.example.com/v1',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  }
};

/**
 * 核心统一请求方法
 * @param {string} endpoint 接口相对路径或绝对地址
 * @param {object} options 请求配置
 */
async function request(endpoint, options = {}) {
  const {
    method = 'GET',
    params,
    body,
    headers = {},
    timeout = DEFAULT_CONFIG.timeout,
    ...customOptions
  } = options;

  // 1. 拼接完整 URL
  let fullUrl = endpoint.startsWith('http')
    ? endpoint
    : `${DEFAULT_CONFIG.baseURL.replace(/\/+$/, '')}/${endpoint.replace(/^\/+/, '')}`;

  if (params && Object.keys(params).length > 0) {
    const searchParams = new URLSearchParams(params);
    fullUrl += (fullUrl.includes('?') ? '&' : '?') + searchParams.toString();
  }

  // 2. 超时信号控制器
  const controller = new AbortController();
  const timer = setTimeout(() => {
    controller.abort(new Error(`网络请求超时: 超过 ${timeout}ms`));
  }, timeout);

  // 3. 读取本地持久化凭据(如 JWT Token)
  const token = localStorage.getItem('auth_token');
  const authHeaders = token ? { 'Authorization': `Bearer ${token}` } : {};

  // 4. 组装请求配置
  const fetchOptions = {
    method,
    headers: {
      ...DEFAULT_CONFIG.headers,
      ...authHeaders,
      ...headers
    },
    signal: controller.signal,
    ...customOptions
  };

  // 5. 序列化请求体
  if (body) {
    if (typeof body === 'object' && !(body instanceof FormData)) {
      fetchOptions.body = JSON.stringify(body);
    } else {
      fetchOptions.body = body;
      // 若为 FormData 则删除预设的 application/json 头
      if (body instanceof FormData) {
        delete fetchOptions.headers['Content-Type'];
      }
    }
  }

  try {
    const response = await fetch(fullUrl, fetchOptions);
    clearTimeout(timer);

    // 6. 响应拦截与状态判定
    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      const error = new Error(errorData.message || `HTTP 错误 ${response.status}`);
      error.status = response.status;
      error.data = errorData;
      throw error;
    }

    // 7. 处理空内容(如 204 No Content)
    if (response.status === 204) {
      return null;
    }

    // 8. 默认按 JSON 解析,若为其他格式降级为文本
    const contentType = response.headers.get('content-type') || '';
    if (contentType.includes('application/json')) {
      return await response.json();
    }
    return await response.text();

  } catch (err) {
    clearTimeout(timer);
    // 处理特定状态码(如 401 跳转登录)
    if (err.status === 401) {
      console.warn('登录已过期,重定向至登录页');
      // window.location.href = '/login';
    }
    throw err;
  }
}

// 便捷语法糖
const http = {
  get: (url, params, options) => request(url, { ...options, method: 'GET', params }),
  post: (url, body, options) => request(url, { ...options, method: 'POST', body }),
  put: (url, body, options) => request(url, { ...options, method: 'PUT', body }),
  delete: (url, options) => request(url, { ...options, method: 'DELETE' })
};

// 项目业务调用示例:
// async function loadUserProfile() {
//   try {
//     const userInfo = await http.get('/user/me');
//     console.log('当前登录用户:', userInfo);
//   } catch (err) {
//     console.error('获取信息异常:', err.message);
//   }
// }

六、关键避坑要点与工程速查

在使用 fetch 编写业务代码时,除了掌握基础模板外,还应牢记以下几项高频易错细节:

  1. 响应流单次读取限制(Body Already Read):Response 对象的实体内容是一个只读的数据流(ReadableStream)。调用诸如 response.json()、response.text() 或 response.blob() 后,数据流即被消费。如果在同一个 response 上连续执行两次解析,会抛出 TypeError: Failed to execute 'json' on 'Response': body stream already read。若必须多次读取,可先调用 response.clone() 创建一份克隆流。
  2. 跨域携带 Cookie 凭证(Credentials):默认情况下,跨域请求不会附带当前站点的 Cookie 与 HTTP 认证凭据。如果接口需要基于 Cookie 的 Session 认证,必须在 options 中显式声明 credentials: 'include'。同时,服务端响应头必须明确设置 Access-Control-Allow-Credentials: true,且 Access-Control-Allow-Origin 不能为通配符 *。
  3. 空响应体解析异常:当服务端返回 204 No Content 或没有响应体的空响应时,直接调用 await response.json() 会导致 JSON 语法解析错误(Unexpected end of JSON input)。在解析前应先检查 response.status !== 204,或通过 response.text() 进行长度兜底判定。
  4. 组件卸载时的内存泄露防范:在 React、Vue 等前端框架中,异步请求尚未结束而组件已销毁时,如果异步回调继续执行 setState,容易引起状态错乱。通过结合 AbortController,在组件销毁钩子(如 React 的 useEffect 卸载回调)中执行 controller.abort(),可彻底杜绝此类悬挂请求。
用JavaScript fetch API发送异步请求的代码模板
图 4:fetch API 异步请求核心开发速查与避坑指南

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章