JavaScript使用fetch API发送GET请求的标准写法

作者:青柠时间:2026-11-03 12:37:53浏览:398

本文深入讲解在现代 JavaScript 中使用 fetch API 发送 GET 请求的标准写法,详细分析 async/await 模式、response.ok 状态检查、查询参数安全拼接、超时控制与完整异常处理方案。

在现代 JavaScript 开发中,使用 fetch API 发送 GET 请求的标准写法是采用 async/await 异步范式,结合 URLSearchParams 规范构建查询参数,显式判断 response.ok 拦截非 2xx 的 HTTP 状态,并在外层通过 try...catch 配合 AbortSignal.timeout() 实现超时控制与网络异常兜底。理解这套标准写法的核心,在于理清请求在“网络传输”与“HTTP业务状态”两个不同维度的表现。
JavaScript使用fetch API发送GET请求的标准写法
图 1:fetch GET 请求的两阶段决议机制与数据流向模型

一、标准 GET 请求的基础范本

在日常业务中,最简洁且稳健的 GET 请求应当兼顾可读性、状态校验与错误兜底。以下是一个直接可运行的标准函数示例:

// 标准 GET 请求函数
async function fetchUserData(userId) {
  const url = `https://api.example.com/users/${userId}`;

  try {
    // 发起请求并注入 8 秒超时信号
    const response = await fetch(url, {
      method: 'GET', // GET 为默认值,显式写出或省略均可
      headers: {
        'Accept': 'application/json'
      },
      signal: AbortSignal.timeout(8000)
    });

    // 步骤关键点:必须主动校验 HTTP 状态码是否正常
    if (!response.ok) {
      throw new Error(`HTTP 错误 [${response.status}]: ${response.statusText}`);
    }

    // 异步流式解析 JSON 数据体
    const data = await response.json();
    return data;

  } catch (error) {
    // 统一捕获网络中断、超时、HTTP异常及解析错误
    console.error('请求失败详情:', error.message);
    throw error; // 向调用方抛出错误,以便上层进行 UI 提示
  }
}

这段范本之所以被视为“标准写法”,是因为它规避了早期 .then().catch() 链式调用中容易出现的逻辑断裂,完整覆盖了请求从发出到落地的每一个必要环节。

二、标准写法的核心步骤与执行机制

理解上述代码不能仅靠死记硬背,必须明白浏览器底层处理 fetch 时的时序与状态变化。一个完整的 GET 流程包含五个确定步骤:

JavaScript使用fetch API发送GET请求的标准写法
图 2:fetch GET 请求的标准执行步骤与状态时序流程

1. 明确省略或配置 GET 方法

在 fetch API 中,当你不传递第二个配置参数,或者配置对象中不指定 method 时,浏览器的底层默认行为就是 GET 请求。因此 fetch(url) 与 fetch(url, { method: 'GET' }) 效果一致。在团队工程中,通常根据团队规范选择是否显式声明 method。

2. 两阶段决议(Two-Phase Resolution)

fetch 设计为流式(Streams)传输,整个异步过程分为两个独立阶段:

  • 第一阶段(首部到达):当服务端返回 HTTP 响应头(Headers)并确定状态码时,await fetch() 的 Promise 即宣告决议(Resolved),并返回一个 Response 对象。此时响应体(Body)的内容可能还在网络传输中。
  • 第二阶段(数据体解析):调用 response.json() 或 response.text() 是第二个异步操作,它会从网络底层以流的形式持续读取二进制数据,全部下载完毕后执行序列化解析。因此必须同样使用 await。

3. 严格检查 response.ok 属性

response.ok 是一个布尔值只读属性。只要 HTTP 状态码处于 200 ~ 299 区间内,该属性即为 true。一旦服务端返回了 400、401、403、404、500 等状态码,response.ok 将变为 false。在此处手动 throw new Error 是阻断错误数据污染业务逻辑的最关键屏障。

三、避免最常见陷阱:为什么 404 和 500 不会进入 catch?

许多初学者经常会写出如下具有重大安全隐患的代码:

// 错误示范:误以为 catch 能兜底所有异常
try {
  const res = await fetch('/api/non-existent');
  const data = await res.json(); // 若服务端 404 返回 HTML 错误页,此处将报 JSON 语法错误
} catch (err) {
  // 这里原本想捕获接口异常,结果只能捕获 JSON 解析失败!
}

这里存在的根本机制差异在于:fetch API 只在遇到“网络层严重故障”时才会将 Promise 标记为 rejected。

JavaScript使用fetch API发送GET请求的标准写法
图 3:Fetch 请求生命周期中的异常分类与捕获机制对比
故障场景 典型原因 Promise 状态 是否自动触发 catch 标准解决手段
网络传输级故障 断网、DNS 解析错误、CORS 跨域被拦截、连接超时 Rejected (抛出 TypeError / AbortError) 是 外层 try...catch 兜底,提示用户检查网络
服务端 HTTP 报错 404 未找到资源、500 服务端内部错误、401 未登录 Resolved (成功返回 Response 实例) 否 显式检查 if (!response.ok) throw ...
响应内容格式错误 服务端在 500 时返回了 HTML 网页而非约定 JSON 解析方法 Promise Rejected 是 先拦截 !response.ok,杜绝直接对错误页调用 json()

四、生产级必备:动态参数、请求头与超时控制

在实际生产环境中,一个纯粹的 URL 往往不能满足复杂查询的需求。标准写法必须妥善解决查询参数转义、HTTP 协商头以及超时释放等工程问题。

JavaScript使用fetch API发送GET请求的标准写法
图 4:现代生产级 fetch GET 请求配置体系架构

1. 使用 URLSearchParams 处理查询字符串

严禁使用普通的字符串加号拼接或模板字符串拼接 URL 参数。例如当参数值包含 &、=、空格或中文字符时,手动拼接会导致解析截断或报错。标准写法是借助原生 URL 和 URLSearchParams 对象:

// 规范的 GET 查询参数装配方法
function buildQueryUrl(endpoint, params = {}) {
  const url = new URL(endpoint, window.location.origin);
  
  // 使用 searchParams 自动完成转义与格式化
  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && value !== null) {
      url.searchParams.set(key, String(value));
    }
  });
  
  return url.toString();
}

// 示例调用:自动处理中文字符和特殊符号
const requestUrl = buildQueryUrl('/api/search', {
  keyword: '前端 & 架构',
  page: 1,
  limit: 20
});
// 生成: /api/search?keyword=%E5%89%8D%E7%AB%AF+%26+%E6%9E%B6%E6%9E%84&page=1&limit=20

2. 超时控制:AbortSignal.timeout 与 AbortController

原生 fetch 默认不设超时时间,若后端无响应,该请求可能会在浏览器中长时间保持挂起,占用浏览器并发连接数并导致前端界面卡死。

  • 简易超时(现代浏览器标准):直接在 options 中传入 signal: AbortSignal.timeout(8000),8 秒未完成即自动中止。
  • 组件解绑与联动取消:在单页应用(React / Vue)中,若组件在请求返回前已经卸载,可使用 const controller = new AbortController(),并将 controller.signal 传给 fetch,在组件清理钩子中调用 controller.abort()。
⚠️ 重要限制:GET 请求严禁包含 body 属性
根据 HTTP 规范与 Fetch 标准,GET 与 HEAD 请求用于安全索取数据,不应当携带负载。如果你在 fetch 的 options 中传入了 body 字段(哪怕是空对象或空字符串),浏览器在解析阶段就会直接抛出 TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body。

五、可直接复用的工程级封装

在项目中,我们通常将上述标准统一封装为一个通用的 GET 工具函数:

/**
 * 标准工程级 GET 请求封装
 * @param {string} url - 目标路径或完整URL
 * @param {Object} options - 请求配置选项
 * @param {Object} [options.params] - 查询参数键值对
 * @param {Object} [options.headers] - 自定义请求头
 * @param {number} [options.timeout=10000] - 超时毫秒数
 * @returns {Promise<any>} 解析后的 JSON 数据
 */
async function httpGet(url, { params, headers = {}, timeout = 10000, ...customConfig } = {}) {
  // 1. 组装具备转义能力的 URL
  const finalUrl = new URL(url, window.location.href);
  if (params) {
    Object.entries(params).forEach(([k, v]) => {
      if (v !== undefined && v !== null) {
        finalUrl.searchParams.set(k, String(v));
      }
    });
  }

  // 2. 超时控制器配置
  const signal = customConfig.signal || AbortSignal.timeout(timeout);

  try {
    const response = await fetch(finalUrl.toString(), {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        ...headers
      },
      signal,
      ...customConfig
    });

    // 3. HTTP 业务状态码拦截
    if (!response.ok) {
      let errorBody = null;
      try {
        errorBody = await response.json();
      } catch (_) {
        errorBody = await response.text().catch(() => null);
      }
      
      const err = new Error(`请求失败,状态码: ${response.status}`);
      err.status = response.status;
      err.data = errorBody;
      throw err;
    }

    // 4. 解析并交付响应体
    return await response.json();

  } catch (error) {
    if (error.name === 'TimeoutError' || error.name === 'AbortError') {
      console.warn(`GET 请求超时或已被中断: ${url}`);
    }
    throw error;
  }
}

六、标准写法快速自查清单

在编写或审查 GET 请求代码时,可对照以下清单快速核验是否符合工业级标准:

JavaScript使用fetch API发送GET请求的标准写法
图 5:JavaScript fetch GET 请求规范自查与最佳实践清单
  • 语法标准:是否使用 async/await 保持单层可读性?是否将 await response.json() 放入了同一结构中?
  • 状态拦截:是否在读取响应体前,强制执行了 if (!response.ok) 检查?
  • 参数处理:是否使用 URLSearchParams 进行查询参数构造,彻底杜绝了手动加号拼装?
  • 健壮容错:是否设定了超时时间(AbortSignal.timeout)?外层 catch 能否同时应对断网与服务端报错?
  • 协议合规:是否确保没有误写 body 参数?
作者最新文章
相关文章
更多