个人技术站点JASONWU

Keep Coding


  • 主页

  • 文章

  • 搜索

JavaScript Fetch API:用法、示例与最佳实践

新建: 2025-07-26 编辑: 2025-08-07   |   分类: 前端开发   | 字数: 4247 字

1. 引言

Fetch API 是现代浏览器内置的网络请求接口,专为取代传统的 XMLHttpRequest 而设计。它基于 Promise 设计,大幅简化了异步 HTTP 请求写法,支持 async/await 语法,使得异步代码更易读和维护。

  • XMLHttpRequest:传统 AJAX 技术的底层 API
  • Promise:异步操作结果的占位符对象,通过 .then() 链式调用,将嵌套结构扁平化
  • async/await:Promise 的语法糖,将异步代码写出同步代码的形式,极大提升可读性与维护性

2. Fetch API vs XHR

使用 Fetch API 向 JSONPlaceholder POST 数据:

JavaScript
 1function postWithFetch() {
 2  const url = 'https://jsonplaceholder.typicode.com/posts';
 3
 4  const params = {
 5    title: '现代 API 测试',
 6    body: '用 Fetch API 发送 POST 请求',
 7    userId: 10
 8  };
 9
10  fetch(url, {
11    method: 'POST',
12    headers: {
13      // 设置请求头:声明本次请求体的数据格式为 JSON
14      'Content-Type': 'application/json'
15    },
16    // 发送数据:对象需序列化为 JSON 字符串
17    body: JSON.stringify(params)
18  })
19  .then(res => {
20    // 检查响应状态码
21    if (!res.ok) {
22      // HTTP Response.ok:响应的状态是否在 200-299 之间
23      // 创建错误对象并抛出,便于下一个 catch 捕获
24      throw new Error(`状态码: ${res.status}`);
25    }
26
27    // 返回解析后的 JSON 数据
28    return res.json();
29  })
30  .then(data => {
31    // 处理解析后的 JSON 数据
32    console.log('Fetch API 返回: ', data);
33  })
34  .catch(err => {
35    // 仅捕获主动 throw 或网络级异常
36    console.error('Fetch API 请求失败: ', err.message);
37  });
38}
39
40// 调用
41postWithFetch();

使用 async/await 语法改写 Fetch API 请求,使代码更简洁易读:

JavaScript
 1async function postWithFetchAsync() {
 2  const url = 'https://jsonplaceholder.typicode.com/posts';
 3
 4  const params = {
 5    title: '现代 API 测试',
 6    body: '用 Fetch API(async/await)发送 POST 请求',
 7    userId: 10
 8  };
 9
10  try {
11    const res = await fetch(url, {
12      method: 'POST',
13      headers: {
14        // 设置请求头:声明本次请求体的数据格式为 JSON
15        'Content-Type': 'application/json'
16      },
17      // 发送数据:对象需序列化为 JSON 字符串
18      body: JSON.stringify(params)
19    });
20
21    // 检查响应状态码
22    if (!res.ok) {
23      // HTTP Response.ok:响应的状态是否在 200-299 之间
24      // 创建错误对象并抛出,便于下一个 catch 捕获
25      throw new Error(`状态码: ${res.status}`);
26    }
27
28    // 解析 JSON 数据
29    const data = await res.json();
30    console.log('Fetch API 返回: ', data);
31  } catch (err) {
32    // 仅捕获主动 throw 或网络级异常
33    console.error('Fetch API 请求失败: ', err.message);
34  }
35}
36
37// 调用
38postWithFetchAsync();

使用 XMLHttpRequest(XHR)传统方法 POST 数据:

JavaScript
 1function postWithXhr() {
 2  const url = 'https://jsonplaceholder.typicode.com/posts';
 3
 4  const xhr = new XMLHttpRequest();
 5  xhr.open('POST', url);
 6
 7  // 可设置跨域时携带 Cookie
 8  // 🚩 注意:同源请求下可自动携带 Cookie,跨域则依赖 `withCredentials`
 9  // 🚩 只有在 `open()` 后、`send()` 前设置才有效
10  // xhr.withCredentials = true;
11
12  // 设置请求头:声明本次请求体的数据格式为 JSON
13  xhr.setRequestHeader('Content-Type', 'application/json');
14
15  // 响应处理
16  // onload:无论状态码是不是 2xx 都会触发
17  xhr.onload = function () {
18    // 🚩 检查 HTTP 状态码是否为成功(2xx)
19    if (xhr.status >= 200 && xhr.status < 300) {
20      // 解析返回 JSON 数据
21      const data = JSON.parse(xhr.responseText);
22      console.log('XHR 返回: ', data);
23    } else {
24      // ⚠️ 非 2xx 状态时也会进来 onload
25      console.error('XHR 请求失败, 状态码: ', xhr.status);
26    }
27  };
28  
29  // onerror:只在网络层出错才会触发(如断网/服务不可达/跨域失败等)
30  xhr.onerror = function () {
31    // ⚠️ 只有网络错误才会触发本回调
32    console.error('XHR 网络异常或被中断');
33  };
34
35  const params = {
36    title: '传统 XHR 测试',
37    body: '用 XMLHttpRequest 发送 POST 请求',
38    userId: 20
39  };
40
41  // 发送数据:对象需序列化为 JSON 字符串
42  xhr.send(JSON.stringify(params));
43}
44
45// 调用
46postWithXhr();

3. Fetch API 语法

Fetch API 完整语法示例(包含常用 options 和返回值处理):

JavaScript
 1fetch('https://api.wuxianjie.net/v1/no', {
 2  // ===== 常用 options 配置 =====
 3  method: 'POST', // 请求方法:'GET'(默认)、'POST'、'PUT'、'DELETE' 等
 4  headers: {
 5    'Content-Type': 'application/json', // 请求头,指定数据格式为 JSON
 6    'Authorization': 'Bearer YOUR_TOKEN' // 可选,添加鉴权信息
 7  },
 8  // 请求体,通常为字符串化的 JSON
 9  body: JSON.stringify({name: 'wxj', age: 18}),
10  // 可设置跨域时携带 Cookie,默认仅在同源请求(same-origin)下自动携带
11  credentials: 'include'
12})
13.then(response => {
14  // ===== 返回值处理(Response 对象)=====
15  if (!response.ok) {
16    // HTTP Response.ok:响应的状态是否在 200-299 之间
17    // ⚠️️ HTTP 状态非 2xx 时不会进入 catch,需要手动抛错
18    throw new Error(`HTTP 错误, 状态码: ${response.status}`);
19  }
20  
21  // 解析为不同数据格式
22  // ⚠️️ 无需 `return await response.json()`,因为后续的 `.then` 会自动展开(flatten)Promise 并继续链式传递
23  return response.json(); // 常用:解析为 JSON 对象
24  // return response.text(); // 可选:解析为纯文本
25  // return response.blob(); // 可选:解析为二进制数据
26})
27.then(data => {
28  // data 是解析后的响应数据
29  // ⚠️️ Promise 链的 `.then` 可以自动把返回的 Promise「展平」,把最终的结果传下去(即 promise resolution)
30  console.log('响应数据: ', data);
31})
32.catch(error => {
33  // ⚠️️ 仅捕获网络错误或上面 throw 抛出的异常
34  console.error('请求异常: ', error);
35})
36.finally(() => {
37  // 无论成功或失败都会执行
38  console.log('请求结束');
39});
  1. 在 Promise 链(即 .then 链)中,每个 .then 接收到上一个 .then 返回的值
  2. 如果上一个 .then 返回的是 Promise(比如 response.json()),则下一个 .then 会在该 Promise resolve 后执行,并接收到 resolve 的结果

🌟 使用 async/await 可以让异步 fetch 代码更简洁、易读、便于异常处理,逻辑更清晰!

JavaScript
 1// 定义异步函数,便于调用 await
 2async function postData() {
 3  try {
 4    const response = await fetch('https://api.wuxianjie.net/v1/no', {
 5      // ===== 常用 options 配置 =====
 6      method: 'POST', // 请求方法:'GET'(默认)、'POST'、'PUT'、'DELETE' 等
 7      headers: {
 8        'Content-Type': 'application/json', // 请求头,指定数据格式为 JSON
 9        'Authorization': 'Bearer YOUR_TOKEN' // 可选,添加鉴权信息
10      },
11      // 请求体,通常为字符串化的 JSON
12      body: JSON.stringify({name: 'wxj', age: 18}),
13      // 可设置跨域时携带 Cookie,默认仅在同源请求(same-origin)下自动携带
14      credentials: 'include'
15    });
16
17    // ===== 返回值处理(Response 对象)=====
18    if (!response.ok) {
19      // HTTP Response.ok:响应的状态是否在 200-299 之间
20      // ⚠️️ HTTP 状态非 2xx 时不会进入 catch,需要手动抛错
21      throw new Error(`HTTP 错误, 状态码: ${response.status}`);
22    }
23
24    // 解析为不同数据格式
25    // ⚠️️ 必需 `await response.json()`,因为需要等待解析完成
26    const data = await response.json(); // 常用:解析为 JSON 对象
27    // const data = await response.text(); // 可选:解析为纯文本
28    // const data = await response.blob(); // 可选:解析为二进制数据
29
30    // data 是解析后的响应数据
31    console.log('响应数据: ', data);
32  } catch (error) {
33    // ⚠️️ 仅捕获网络错误或上面 throw 抛出的异常
34    console.error('请求异常: ', error);
35  } finally {
36    // 无论成功或失败都会执行
37    console.log('请求结束');
38  }
39}
40
41// 调用异步函数
42postData();

上面两种写法功能上效果一致,await 只是实现 .then 的「语法糖」。本质区别:

  • .then 是以回调的形式对 Promise 结果做处理,支持链式多步操作
  • await 是暂停(阻塞)当前 async 函数后续代码、等 Promise 结果出来再继续运行,书写更清晰,异常更好捕捉

4. 注意事项

4.1 Why if (!res.ok)

Fetch API 与传统 XHR 错误处理方式基本一致

  • 只有在「网络异常」时,Fetch 的 Promise 才会被 reject(拒绝),catch/onerror 才会触发
    • 🚫 网络连接失败(如断网、服务器不可达、DNS 解析失败等)
    • 🚫 CORS(Cross-Origin Resource Sharing,跨域资源共享)策略拦截
    • 🚫 主动终止请求(如调用 AbortController.abort)
    • 此时抛出的通常是 TypeError(类型错误)
  • 但如果只是 HTTP 错误(如 404、500),Promise 依然会 resolve(解决),只是 response.ok === false,需要手动判断状态码做相应处理

Fetch/XHR 与 Axios 的最大不同

  • Axios 会自动将 HTTP 状态码非 2xx 的响应作为错误(reject promise,自动进入 catch),无需手动判断
  • Axios 的 error 对象内部还包含了丰富的错误信息(如 error.response、error.request、error.message),便于精细化处理

Fetch API 错误处理写法:

JavaScript
 1async function fetchData() {
 2  try {
 3    const res = await fetch('https://api.wuxianjie.net/v1/no');
 4    // 🌟 必须手动检查 HTTP 状态码
 5    if (!res.ok) {
 6      throw new Error(`HTTP 错误! 状态码: ${res.status}`); // 可自定义错误信息
 7    }
 8    const data = await res.json();
 9    console.log('数据: ', data);
10  } catch (error) {
11    // 只有网络层异常才会走这里,如断网/CORS
12    console.error('Fetch 异常: ', error);
13  }
14}
15
16// 调用函数
17fetchData();

Axios 错误处理(以请求 DummyJSON API 为例)写法:

HTML
 1<html>
 2  <body>
 3    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
 4    <script>
 5      async function fetchDataAxios() {
 6        try {
 7          // 不存在的 URL
 8          // const res = await axios.get('https://api.wuxianjie.net/v1/no');
 9          
10          // 模拟响应 400 错误
11          const res = await axios.get('https://dummyjson.com/http/400');
12          console.log('数据: ', res.data);
13        } catch (err) {
14          // HTTP 错误(如 404,500)也会自动走这里
15          if (err.response) {
16            // 服务器响应了,且状态码非 2xx
17            console.error(
18              'Axios HTTP 错误: ',
19              err.response.status,
20              err.response.data
21            );
22          } else if (err.request) {
23            // 请求已发出但没有收到响应,当访问不存在的 URL 时会触发
24            console.error('未收到响应: ', err.request);
25          } else {
26            // 配置等其它错误
27            console.error('Axios 异常: ', err.message);
28          }
29        }
30      }
31
32      // 调用函数
33      fetchDataAxios();
34    </script>
35  </body>
36</html>

4.2 Why await response.json()

  • fetch(url) 返回一个 Promise,解析后得到 Response 对象
  • response.json() 等「读取响应体内容(如 .json()、.text()、.blob())」也是异步操作,同样返回 Promise
  • 只有「先 await fetch,再 await response.json」才能确保最终拿到的是真正的 JSON 数据,而不是 Promise 对象

常见困惑

  • 误以为 fetch 后的 response 已经拿到了内容,其实只是响应头和状态,body(主体)还未解析
  • 若省略 await,则在执行 console.log(response.json()),看到的永远是 Promise{<pending>}
JavaScript
 1// 封装 API 调用
 2async function fetchVersion() {
 3  const res = await fetch('http://localhost:8890/wxj/api/v1/public/version', {
 4    method: 'GET',
 5    headers: {
 6      'Accept-Language': 'zh'
 7    }
 8  });
 9
10  if (!res.ok) {
11    const errData = await res.json();
12    throw new Error(`${errData.error || JSON.stringify(errData)}`);
13  }
14
15  // ‼️ 这里未使用 `await`,而是直接获取 `res.json()` 的结果
16  const data = res.json();
17
18  // 输出的是 `Promise{<pending>}`,因为此时 `data` 只是还未完成的 `Promise`
19  console.log(data);
20
21  return data;
22}
23
24try {
25  // ❓ 为何最终可以获取 API 结果
26  // 此处 await 已是真正等待 Promise 完成,故拿到实际数据
27  const versionData = await fetchVersion();
28  console.log('API 调用成功: ', versionData);
29} catch (err) {
30  console.error('API 调用失败: ', err.message);
31}

5. TS 最佳实践

TypeScript
 1// HTTP JSON API 工具函数:封装 fetch 请求,自动处理 JSON 响应和错误
 2async function fetchJson<T>(url: string, options?: RequestInit): Promise<T> {
 3  let res: Response;
 4  try {
 5    res = await fetch(url, options);
 6  } catch (err) {
 7    // 捕获网络异常(如 DNS、连接超时等)
 8    throw new Error(`[网络错误] 请求失败: ${url}, 原因为: ${(err as Error).message}`);
 9  }
10
11  let data: any;
12  try {
13    data = await res.json();
14  } catch {
15    // 若解析 JSON 失败,则尝试读取 text
16    const text = await res.text();
17    throw new Error(`[响应解析异常] 非标准 JSON 响应: ${text}`);
18  }
19
20  if (!res.ok) {
21    throw new Error(`[服务端错误] ${res.status} ${res.statusText} => ${(data && (data.error || JSON.stringify(data)))}, 请求路径: ${url}`);
22  }
23
24  return data as T;
25}
26
27// ===== 用法演示 =====
28type VersionApi = {
29  version: string;
30  description: string;
31  author: string;
32};
33
34async function demo() {
35  // 不带请求头
36  fetchJson<VersionApi>('http://localhost:8890/wxj/api/v1/public/version')
37  .then(data => {
38    console.log('[成功] 不指定请求头获取数据:', data);
39  })
40  .catch(err => {
41    console.error('[失败] 不指定请求头获取数据:', err.message);
42  });
43
44  // 带 Accept-Language 头
45  fetchJson<VersionApi>('http://localhost:8890/wxj/api/v1/public/version', {
46    headers: {'Accept-Language': 'zh'}
47  })
48  .then(data => {
49    console.log('[成功] 指定请求头获取数据:', data);
50  })
51  .catch(err => {
52    console.error('[失败] 指定请求头获取数据:', err.message);
53  });
54}
55
56demo();
#JavaScript# #TypeScript# #Promise# #网络请求# #异步编程#

文章:JavaScript Fetch API:用法、示例与最佳实践

链接:https://www.wuxianjie.net/posts/frontend/js-async-fetch/

作者:吴仙杰

文章: 本博客文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议,转载请注明出处!

Bash 命令行提效
Linux 文件系统
  • 文章目录
  • 站点概览
吴仙杰

吴仙杰

🔍 Ctrl+K / ⌘K

27 文章
9 分类
25 标签
邮箱 GitHub
  • 1. 引言
  • 2. Fetch API vs XHR
  • 3. Fetch API 语法
  • 4. 注意事项
    • 4.1 Why if (!res.ok)
    • 4.2 Why await response.json()
  • 5. TS 最佳实践
© 2021-2025 吴仙杰 保留所有权利 All Rights Reserved
浙公网安备 33010302003726号 浙ICP备2021017187号-1
0%