xhr、fetch和axios [复制链接]
发表于 2025-7-20 19:17:20 | 显示全部楼层 |阅读模式
XMLHttpRequest (XHR)

XMLHttpRequest 是最早用于在浏览器中进行异步网络请求的 API。它答应网页在不革新整个页面的情况下与服务器交换数据。
  1. // 创建 XHR 对象
  2. const xhr = new XMLHttpRequest();
  3. // 初始化请求
  4. xhr.open('GET', 'https://api.example.com/data', true);
  5. // 设置请求头(可选)
  6. xhr.setRequestHeader('Content-Type', 'application/json');
  7. // 设置响应类型(可选)
  8. xhr.responseType = 'json';
  9. // 监听状态变化
  10. xhr.onreadystatechange = function() {
  11.   if (xhr.readyState === 4) {
  12.     if (xhr.status === 200) {
  13.       // 请求成功
  14.       console.log('Response:', xhr.response);
  15.     } else {
  16.       // 请求失败
  17.       console.error('Error:', xhr.status);
  18.     }
  19.   }
  20. };
  21. // 可选:处理错误
  22. xhr.onerror = function() {
  23.   console.error('Network error occurred');
  24. };
  25. // 发送请求
  26. xhr.send();
复制代码

  • 优点:广泛兼容、支持进度变乱、功能全面
  • 缺点API 复杂、回调嵌套问题(回调地狱)、不支持 Promise
回调地狱问题
  1. // 第一个请求
  2. const xhr1 = new XMLHttpRequest();
  3. xhr1.open('GET', '/api/data1', true);
  4. xhr1.onreadystatechange = function() {
  5.   if (xhr1.readyState === 4 && xhr1.status === 200) {
  6.     const data1 = JSON.parse(xhr1.responseText);
  7.    
  8.     // 第二个请求(依赖第一个请求的结果)
  9.     const xhr2 = new XMLHttpRequest();
  10.     xhr2.open('GET', `/api/data2?param=${data1.id}`, true);
  11.     xhr2.onreadystatechange = function() {
  12.       if (xhr2.readyState === 4 && xhr2.status === 200) {
  13.         const data2 = JSON.parse(xhr2.responseText);
  14.         
  15.         // 第三个请求(依赖第二个请求的结果)
  16.         const xhr3 = new XMLHttpRequest();
  17.         xhr3.open('POST', '/api/submit', true);
  18.         xhr3.setRequestHeader('Content-Type', 'application/json');
  19.         xhr3.onreadystatechange = function() {
  20.           if (xhr3.readyState === 4 && xhr3.status === 200) {
  21.             const result = JSON.parse(xhr3.responseText);
  22.             console.log('最终结果:', result);
  23.           }
  24.         };
  25.         xhr3.send(JSON.stringify({ data1, data2 }));
  26.       }
  27.     };
  28.     xhr2.send();
  29.   }
  30. };
  31. xhr1.send();
复制代码
为什么不能按顺序写 XHR 请求?
在异步编程中,代码誊写顺序实行顺序是两回事。当你按顺序写 XHR 请求时,它们会立刻开始实行,但不会等待前一个请求完成。这会导致严重问题:
  1. // 错误示例:按顺序写但不嵌套
  2. const xhr1 = new XMLHttpRequest();
  3. xhr1.open('GET', '/api/data1', true);
  4. xhr1.onreadystatechange = function() { /* 处理 data1 */ };
  5. xhr1.send();
  6. const xhr2 = new XMLHttpRequest();
  7. xhr2.open('GET', '/api/data2?param=???', true); // 这里需要 data1.id,但 data1 可能还没返回
  8. xhr2.onreadystatechange = function() { /* 处理 data2 */ };
  9. xhr2.send();
  10. const xhr3 = new XMLHttpRequest();
  11. xhr3.open('POST', '/api/submit', true);
  12. xhr3.setRequestHeader('Content-Type', 'application/json');
  13. xhr3.onreadystatechange = function() { /* 处理结果 */ };
  14. xhr3.send(JSON.stringify({ data1, data2 })); // data1 和 data2 都可能不存在
复制代码
为什么会出错?

  •         数据依靠问题

    •                 第二个请求必要第一个请求的结果 (data1.id)
    •                 第三个请求必要前两个请求的结果 (data1 和 data2)
           
  •         实行顺序不确定

    •                 异步请求的完成时间不可预测
    •                 即使请求 1 先发送,请求 2 和 3 可能先完成
           
  •         闭包问题

    •                 回调函数中的变量会捕捉外部作用域
    •                 假如不嵌套,变量可能在回调实行时已被修改
           
嵌套的核心目的:确保实行顺序
通过嵌套回调,你实际上是在告诉步伐:
"只有当请求 1 乐成完成后,才开始请求 2;只有当请求 2 乐成完成后,才开始请求 3"

解决方案

    


Fetch API


Fetch API 是现代浏览器提供的用于替换 XHR 的新标准,它基于 Promise,提供了更轻便、更强大的接口来处置惩罚网络请求。
重要特点:

  • 基于 Promise,避免回调地狱
  • 支持 async/await 语法
  • 提供 Request 和 Response 对象
  • 支持跨域请求和 CORS
  • 支持流式响应体处置惩罚
基本用法
  1. // 简单的 GET 请求
  2. fetch('https://api.example.com/data')
  3.   .then(response => {
  4.     if (!response.ok) {
  5.       throw new Error(`HTTP error! Status: ${response.status}`);
  6.     }
  7.     return response.json(); // 解析为 JSON
  8.   })
  9.   .then(data => {
  10.     console.log('Response:', data);
  11.   })
  12.   .catch(error => {
  13.     console.error('Fetch error:', error);
  14.   });
  15. // 使用 async/await
  16. async function fetchData() {
  17.   try {
  18.     const response = await fetch('https://api.example.com/data');
  19.     if (!response.ok) {
  20.       throw new Error(`HTTP error! Status: ${response.status}`);
  21.     }
  22.     const data = await response.json();
  23.     console.log('Response:', data);
  24.   } catch (error) {
  25.     console.error('Fetch error:', error);
  26.   }
  27. }
  28. // 带参数的 POST 请求
  29. fetch('https://api.example.com/submit', {
  30.   method: 'POST',
  31.   headers: {
  32.     'Content-Type': 'application/json',
  33.   },
  34.   body: JSON.stringify({ name: 'John', age: 30 }),
  35. })
  36.   .then(response => response.json())
  37.   .then(data => console.log(data))
  38.   .catch(error => console.error('Error:', error));
复制代码
优缺点:

  • 优点:现代 API、基于 Promise、更轻便、支持流式处置惩罚
  • 缺点:不支持进度变乱、错误处置惩罚必要额外查抄状态码、旧浏览器兼容性差(必要 polyfill)

Fetch API 的局限性详解

1. 不支持进度变乱

Fetch API 的重要计划目的是提供一个现代、轻便的网络请求接口,但它没有内置的进度变乱支持。在上传或下载大文件时,这是一个显着的不足:
XHR 的进度支持:XHR 通过 onprogress 变乱提供上传和下载进度:
  1. xhr.upload.onprogress = function(event) {
  2.   if (event.lengthComputable) {
  3.     const percentComplete = (event.loaded / event.total) * 100;
  4.     console.log(`上传进度: ${percentComplete}%`);
  5.   }
  6. };
复制代码
Fetch 的替换方案:Fetch 必要使用更复杂的 ReadableStream API 来实现进度:
  1. fetch('large-file.zip')
  2.   .then(response => {
  3.     const reader = response.body.getReader();
  4.     const contentLength = response.headers.get('Content-Length');
  5.     let receivedLength = 0;
  6.    
  7.     return new Response(new ReadableStream({
  8.       async start(controller) {
  9.         while (true) {
  10.           const { done, value } = await reader.read();
  11.           if (done) break;
  12.           receivedLength += value.length;
  13.           const percentComplete = (receivedLength / contentLength) * 100;
  14.           console.log(`下载进度: ${percentComplete}%`);
  15.           controller.enqueue(value);
  16.         }
  17.         controller.close();
  18.       }
  19.     }));
  20.   });
复制代码


2. 错误处置惩罚必要额外查抄状态码

Fetch API 的计划与传统 HTTP 错误处置惩罚模式不同:

  • Fetch 的错误处置惩罚机制

    • 只有网络错误(如断网、DNS 失败)才会触发 reject
    • HTTP 错误(如 404、500)不会触发 reject,而是返回一个状态码非 2xx 的 Response 对象
           
  1. fetch('https://example.com/non-existent')
  2.   .then(response => {
  3.     // 这里会执行,即使服务器返回 404
  4.     if (!response.ok) {
  5.       throw new Error(`HTTP error! status: ${response.status}`);
  6.     }
  7.     return response.json();
  8.   })
  9.   .catch(error => {
  10.     console.error('Fetch error:', error);
  11.   });
复制代码
XHR 的错误处置惩罚:XHR 的 onerror 变乱会捕捉网络错误,而 HTTP 错误通过状态码判断:
  1. xhr.onerror = function() {
  2.   console.error('网络错误');
  3. };
  4. xhr.onreadystatechange = function() {
  5.   if (xhr.readyState === 4) {
  6.     if (xhr.status >= 400) {
  7.       console.error('HTTP 错误:', xhr.status);
  8.     }
  9.   }
  10. };
复制代码
3. 旧浏览器兼容性差(必要 polyfill)



AXIOS和fetch差异

Axios 是一个基于 Promise 的 HTTP 客户端,专为浏览器和 Node.js 计划。它与原生 Fetch API 有很多区别,这些区别影响着开发者的选择。



具体差异详解

1 错误处置惩罚

Axios:HTTP 错误(404、500 等)会直接 reject Promise
  1. axios.get('/api/data')
  2.   .then(response => {
  3.     // 仅在 HTTP 状态码为 2xx 时执行
  4.     console.log(response.data);
  5.   })
  6.   .catch(error => {
  7.     // 处理所有错误(网络错误和 HTTP 错误)
  8.     console.error('请求失败:', error.response?.status);
  9.   });
复制代码
Fetch:HTTP 错误不会 reject,必要手动查抄状态码
在 Fetch 请求的 then 回调中,你必要查抄 response.ok 属性或 response.status 状态码:
  1. fetch('https://api.example.com/data')
  2.   .then(response => {
  3.     // 检查响应状态
  4.     if (!response.ok) {
  5.       // 手动抛出错误,使 Promise 被 reject
  6.       throw new Error(`HTTP error! status: ${response.status}`);
  7.     }
  8.     // 请求成功,继续处理响应
  9.     return response.json();
  10.   })
  11.   .then(data => console.log('数据:', data))
  12.   .catch(error => console.error('请求失败:', error));
复制代码
2 数据格式处置惩罚

Axios:自动解析 JSON 响应
  1. axios.get('/api/data')
  2.   .then(response => {
  3.     // response.data 已经是解析后的 JSON 对象
  4.     console.log(response.data.name);
  5.   });
复制代码
Fetch:必要手动解析响应
  1. fetch('/api/data')
  2.   .then(response => response.json()) // 手动解析为 JSON
  3.   .then(data => console.log(data.name));
复制代码
3 请求取消

Axios:使用 CancelToken
  1. const source = axios.CancelToken.source();
  2. axios.get('/api/data', {
  3.   cancelToken: source.token
  4. })
  5. .then(response => console.log(response))
  6. .catch(thrown => {
  7.   if (axios.isCancel(thrown)) {
  8.     console.log('请求被取消:', thrown.message);
  9.   }
  10. });
  11. // 取消请求
  12. source.cancel('用户取消了请求');
复制代码
Fetch:使用 AbortController(必要现代浏览器支持)
  1. const controller = new AbortController();
  2. const signal = controller.signal;
  3. fetch('/api/data', { signal })
  4.   .then(response => response.json())
  5.   .then(data => console.log(data))
  6.   .catch(error => {
  7.     if (error.name === 'AbortError') {
  8.       console.log('请求被取消');
  9.     }
  10.   });
  11. // 取消请求
  12. controller.abort();
复制代码
4 进度变乱

Axios:内置支持上传和下载进度
  1. // 下载进度
  2. axios.get('/large-file', {
  3.   onDownloadProgress: progressEvent => {
  4.     const percentCompleted = Math.round(
  5.       (progressEvent.loaded * 100) / progressEvent.total
  6.     );
  7.     console.log(`下载进度: ${percentCompleted}%`);
  8.   }
  9. });
  10. // 上传进度
  11. axios.post('/upload', formData, {
  12.   onUploadProgress: progressEvent => {
  13.     const percentCompleted = Math.round(
  14.       (progressEvent.loaded * 100) / progressEvent.total
  15.     );
  16.     console.log(`上传进度: ${percentCompleted}%`);
  17.   }
  18. });
复制代码
Fetch:必要使用 ReadableStream 手动实现
  1. fetch('/large-file')
  2.   .then(response => {
  3.     const contentLength = response.headers.get('Content-Length');
  4.     const reader = response.body.getReader();
  5.     let receivedLength = 0;
  6.    
  7.     return new Response(new ReadableStream({
  8.       async start(controller) {
  9.         while (true) {
  10.           const { done, value } = await reader.read();
  11.           if (done) break;
  12.           receivedLength += value.length;
  13.           const percentComplete = (receivedLength / contentLength) * 100;
  14.           console.log(`下载进度: ${percentComplete}%`);
  15.           controller.enqueue(value);
  16.         }
  17.         controller.close();
  18.       }
  19.     }));
  20.   });
复制代码
5 拦截器

Axios:支持请求和响应拦截器,便于统一处置惩罚
  1. // 添加请求拦截器
  2. axios.interceptors.request.use(
  3.   config => {
  4.     // 在发送请求之前做些什么
  5.     config.headers.Authorization = `Bearer ${token}`;
  6.     return config;
  7.   },
  8.   error => {
  9.     // 对请求错误做些什么
  10.     return Promise.reject(error);
  11.   }
  12. );
  13. // 添加响应拦截器
  14. axios.interceptors.response.use(
  15.   response => {
  16.     // 对响应数据做点什么
  17.     return response;
  18.   },
  19.   error => {
  20.     // 对响应错误做点什么
  21.     if (error.response.status === 401) {
  22.       // 处理未授权情况
  23.     }
  24.     return Promise.reject(error);
  25.   }
  26. );
复制代码
Fetch:不直接支持拦截器,必要手动实现
  1. function fetchWithInterceptor(url, options) {
  2.   // 请求拦截
  3.   const modifiedOptions = {
  4.     ...options,
  5.     headers: {
  6.       ...options.headers,
  7.       Authorization: `Bearer ${token}`
  8.     }
  9.   };
  10.   
  11.   return fetch(url, modifiedOptions)
  12.     .then(response => {
  13.       // 响应拦截
  14.       if (response.status === 401) {
  15.         // 处理未授权情况
  16.       }
  17.       return response;
  18.     });
  19. }
复制代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表