xhr、fetch和axios

XMLHttpRequest (XHR)

XMLHttpRequest 是最早用于在浏览器中进行异步网络请求的 API。它允许网页在不刷新整个页面的情况下与服务器交换数据。

javascript 复制代码
// 创建 XHR 对象
const xhr = new XMLHttpRequest();

// 初始化请求
xhr.open('GET', 'https://api.example.com/data', true);

// 设置请求头(可选)
xhr.setRequestHeader('Content-Type', 'application/json');

// 设置响应类型(可选)
xhr.responseType = 'json';

// 监听状态变化
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // 请求成功
      console.log('Response:', xhr.response);
    } else {
      // 请求失败
      console.error('Error:', xhr.status);
    }
  }
};

// 可选:处理错误
xhr.onerror = function() {
  console.error('Network error occurred');
};

// 发送请求
xhr.send();
  • 优点:广泛兼容、支持进度事件、功能全面
  • 缺点:API 复杂、回调嵌套问题(回调地狱)、不支持 Promise

回调地狱问题

javascript 复制代码
// 第一个请求
const xhr1 = new XMLHttpRequest();
xhr1.open('GET', '/api/data1', true);
xhr1.onreadystatechange = function() {
  if (xhr1.readyState === 4 && xhr1.status === 200) {
    const data1 = JSON.parse(xhr1.responseText);
    
    // 第二个请求(依赖第一个请求的结果)
    const xhr2 = new XMLHttpRequest();
    xhr2.open('GET', `/api/data2?param=${data1.id}`, true);
    xhr2.onreadystatechange = function() {
      if (xhr2.readyState === 4 && xhr2.status === 200) {
        const data2 = JSON.parse(xhr2.responseText);
        
        // 第三个请求(依赖第二个请求的结果)
        const xhr3 = new XMLHttpRequest();
        xhr3.open('POST', '/api/submit', true);
        xhr3.setRequestHeader('Content-Type', 'application/json');
        xhr3.onreadystatechange = function() {
          if (xhr3.readyState === 4 && xhr3.status === 200) {
            const result = JSON.parse(xhr3.responseText);
            console.log('最终结果:', result);
          }
        };
        xhr3.send(JSON.stringify({ data1, data2 }));
      }
    };
    xhr2.send();
  }
};
xhr1.send();

为什么不能按顺序写 XHR 请求?

在异步编程中,代码的书写顺序执行顺序是两回事。当你按顺序写 XHR 请求时,它们会立即开始执行,但不会等待前一个请求完成。这会导致严重问题:

javascript 复制代码
// 错误示例:按顺序写但不嵌套
const xhr1 = new XMLHttpRequest();
xhr1.open('GET', '/api/data1', true);
xhr1.onreadystatechange = function() { /* 处理 data1 */ };
xhr1.send();

const xhr2 = new XMLHttpRequest();
xhr2.open('GET', '/api/data2?param=???', true); // 这里需要 data1.id,但 data1 可能还没返回
xhr2.onreadystatechange = function() { /* 处理 data2 */ };
xhr2.send();

const xhr3 = new XMLHttpRequest();
xhr3.open('POST', '/api/submit', true);
xhr3.setRequestHeader('Content-Type', 'application/json');
xhr3.onreadystatechange = function() { /* 处理结果 */ };
xhr3.send(JSON.stringify({ data1, data2 })); // data1 和 data2 都可能不存在

为什么会出错?

  1. 数据依赖问题

    • 第二个请求需要第一个请求的结果 (data1.id)

    • 第三个请求需要前两个请求的结果 (data1data2)

  2. 执行顺序不确定

    • 异步请求的完成时间不可预测

    • 即使请求 1 先发送,请求 2 和 3 可能先完成

  3. 闭包问题

    • 回调函数中的变量会捕获外部作用域

    • 如果不嵌套,变量可能在回调执行时已被修改

嵌套的核心目的:确保执行顺序

通过嵌套回调,你实际上是在告诉程序:

"只有当请求 1 成功完成后,才开始请求 2;只有当请求 2 成功完成后,才开始请求 3"

解决方案

Fetch API

Fetch API 是现代浏览器提供的用于替代 XHR 的新标准,它基于 Promise,提供了更简洁、更强大的接口来处理网络请求。

主要特点:

  • 基于 Promise,避免回调地狱
  • 支持 async/await 语法
  • 提供 Request 和 Response 对象
  • 支持跨域请求和 CORS
  • 支持流式响应体处理

基本用法

javascript 复制代码
// 简单的 GET 请求
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json(); // 解析为 JSON
  })
  .then(data => {
    console.log('Response:', data);
  })
  .catch(error => {
    console.error('Fetch error:', error);
  });

// 使用 async/await
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log('Response:', data);
  } catch (error) {
    console.error('Fetch error:', error);
  }
}

// 带参数的 POST 请求
fetch('https://api.example.com/submit', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'John', age: 30 }),
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

优缺点:

  • 优点:现代 API、基于 Promise、更简洁、支持流式处理
  • 缺点:不支持进度事件、错误处理需要额外检查状态码、旧浏览器兼容性差(需要 polyfill)

Fetch API 的局限性详解

1. 不支持进度事件

Fetch API 的主要设计目标是提供一个现代、简洁的网络请求接口,但它没有内置的进度事件支持。在上传或下载大文件时,这是一个明显的不足:

XHR 的进度支持 :XHR 通过 onprogress 事件提供上传和下载进度:

javascript 复制代码
xhr.upload.onprogress = function(event) {
  if (event.lengthComputable) {
    const percentComplete = (event.loaded / event.total) * 100;
    console.log(`上传进度: ${percentComplete}%`);
  }
};

Fetch 的替代方案 :Fetch 需要使用更复杂的 ReadableStream API 来实现进度:

javascript 复制代码
fetch('large-file.zip')
  .then(response => {
    const reader = response.body.getReader();
    const contentLength = response.headers.get('Content-Length');
    let receivedLength = 0;
    
    return new Response(new ReadableStream({
      async start(controller) {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          receivedLength += value.length;
          const percentComplete = (receivedLength / contentLength) * 100;
          console.log(`下载进度: ${percentComplete}%`);
          controller.enqueue(value);
        }
        controller.close();
      }
    }));
  });
2. 错误处理需要额外检查状态码

Fetch API 的设计与传统 HTTP 错误处理模式不同:

  • Fetch 的错误处理机制
    • 只有网络错误 (如断网、DNS 失败)才会触发 reject
    • HTTP 错误(如 404、500)不会触发 reject,而是返回一个状态码非 2xx 的 Response 对象
javascript 复制代码
fetch('https://example.com/non-existent')
  .then(response => {
    // 这里会执行,即使服务器返回 404
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .catch(error => {
    console.error('Fetch error:', error);
  });

XHR 的错误处理 :XHR 的 onerror 事件会捕获网络错误,而 HTTP 错误通过状态码判断:

javascript 复制代码
xhr.onerror = function() {
  console.error('网络错误');
};

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status >= 400) {
      console.error('HTTP 错误:', xhr.status);
    }
  }
};
3. 旧浏览器兼容性差(需要 polyfill)

AXIOS和fetch差别

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

具体差异详解
1 错误处理

Axios:HTTP 错误(404、500 等)会直接 reject Promise

javascript 复制代码
axios.get('/api/data')
  .then(response => {
    // 仅在 HTTP 状态码为 2xx 时执行
    console.log(response.data);
  })
  .catch(error => {
    // 处理所有错误(网络错误和 HTTP 错误)
    console.error('请求失败:', error.response?.status);
  });

Fetch:HTTP 错误不会 reject,需要手动检查状态码

在 Fetch 请求的 then 回调中,你需要检查 response.ok 属性或 response.status 状态码:

javascript 复制代码
fetch('https://api.example.com/data')
  .then(response => {
    // 检查响应状态
    if (!response.ok) {
      // 手动抛出错误,使 Promise 被 reject
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    // 请求成功,继续处理响应
    return response.json();
  })
  .then(data => console.log('数据:', data))
  .catch(error => console.error('请求失败:', error));
2 数据格式处理

Axios:自动解析 JSON 响应

javascript 复制代码
axios.get('/api/data')
  .then(response => {
    // response.data 已经是解析后的 JSON 对象
    console.log(response.data.name);
  });

Fetch:需要手动解析响应

javascript 复制代码
fetch('/api/data')
  .then(response => response.json()) // 手动解析为 JSON
  .then(data => console.log(data.name));
3 请求取消

Axios :使用 CancelToken

javascript 复制代码
const source = axios.CancelToken.source();

axios.get('/api/data', {
  cancelToken: source.token
})
.then(response => console.log(response))
.catch(thrown => {
  if (axios.isCancel(thrown)) {
    console.log('请求被取消:', thrown.message);
  }
});

// 取消请求
source.cancel('用户取消了请求');

Fetch :使用 AbortController(需要现代浏览器支持)

javascript 复制代码
const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('请求被取消');
    }
  });

// 取消请求
controller.abort();
4 进度事件

Axios:内置支持上传和下载进度

javascript 复制代码
// 下载进度
axios.get('/large-file', {
  onDownloadProgress: progressEvent => {
    const percentCompleted = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total
    );
    console.log(`下载进度: ${percentCompleted}%`);
  }
});

// 上传进度
axios.post('/upload', formData, {
  onUploadProgress: progressEvent => {
    const percentCompleted = Math.round(
      (progressEvent.loaded * 100) / progressEvent.total
    );
    console.log(`上传进度: ${percentCompleted}%`);
  }
});

Fetch:需要使用 ReadableStream 手动实现

javascript 复制代码
fetch('/large-file')
  .then(response => {
    const contentLength = response.headers.get('Content-Length');
    const reader = response.body.getReader();
    let receivedLength = 0;
    
    return new Response(new ReadableStream({
      async start(controller) {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          receivedLength += value.length;
          const percentComplete = (receivedLength / contentLength) * 100;
          console.log(`下载进度: ${percentComplete}%`);
          controller.enqueue(value);
        }
        controller.close();
      }
    }));
  });
5 拦截器

Axios:支持请求和响应拦截器,便于统一处理

javascript 复制代码
// 添加请求拦截器
axios.interceptors.request.use(
  config => {
    // 在发送请求之前做些什么
    config.headers.Authorization = `Bearer ${token}`;
    return config;
  },
  error => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
axios.interceptors.response.use(
  response => {
    // 对响应数据做点什么
    return response;
  },
  error => {
    // 对响应错误做点什么
    if (error.response.status === 401) {
      // 处理未授权情况
    }
    return Promise.reject(error);
  }
);

Fetch:不直接支持拦截器,需要手动实现

javascript 复制代码
function fetchWithInterceptor(url, options) {
  // 请求拦截
  const modifiedOptions = {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${token}`
    }
  };
  
  return fetch(url, modifiedOptions)
    .then(response => {
      // 响应拦截
      if (response.status === 401) {
        // 处理未授权情况
      }
      return response;
    });
}
相关推荐
鱼雀AIGC6 分钟前
如何仅用AI开发完整的小程序<6>—让AI对视觉效果进行升级
前端·人工智能·游戏·小程序·aigc·ai编程
duanyuehuan34 分钟前
Vue 组件定义方式的区别
前端·javascript·vue.js
veminhe38 分钟前
HTML5简介
前端·html·html5
洪洪呀39 分钟前
css上下滚动文字
前端·css
搏博2 小时前
基于Vue.js的图书管理系统前端界面设计
前端·javascript·vue.js·前端框架·数据可视化
掘金安东尼2 小时前
前端周刊第419期(2025年6月16日–6月22日)
前端·javascript·面试
bemyrunningdog2 小时前
AntDesignPro前后端权限按钮系统实现
前端
重阳微噪2 小时前
Data Config Admin - 优雅的管理配置文件
前端
Hilaku2 小时前
20MB 的字体文件太大了,我们把 Icon Font 压成了 10KB
前端·javascript·css
fs哆哆2 小时前
在VB.net中,文本插入的几个自定义函数
服务器·前端·javascript·html·.net