Axios 教程 - 基于 Promise 的 HTTP 客户端

项目概述

Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js 环境,提供了简洁、强大的 API 来处理 HTTP 请求。

核心功能

  1. HTTP 请求:支持 GET、POST、PUT、DELETE 等 HTTP 方法
  2. 拦截器:支持请求和响应拦截器
  3. 转换请求和响应:自动转换 JSON 数据,支持自定义转换
  4. 自动转换 JSON 数据:自动将请求和响应数据转换为 JSON
  5. 取消请求:支持取消正在进行的请求
  6. 超时处理:支持设置请求超时
  7. 错误处理:统一的错误处理机制
  8. 并发请求:支持同时发送多个请求
  9. 浏览器支持:支持所有现代浏览器
  10. Node.js 支持:支持 Node.js 环境
  11. TypeScript 支持:良好的 TypeScript 类型定义

安装与设置

基本安装

# 安装 Axios
npm install axios

# 安装特定版本
npm install axios@1.6.0

基本设置

// 导入 Axios
import axios from 'axios';

// 或者使用 CommonJS 导入
const axios = require('axios');

// 创建一个实例
const instance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 1000,
  headers: {
    'Content-Type': 'application/json'
  }
});

基本使用

发送 GET 请求

// 基本 GET 请求
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.log(error);
  });

// 使用参数对象
axios.get('/user', {
  params: {
    ID: 12345
  }
})
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.log(error);
  });

// 使用 async/await
async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response.data);
  } catch (error) {
    console.log(error);
  }
}

发送 POST 请求

// 基本 POST 请求
axios.post('/user', {
  firstName: 'John',
  lastName: 'Doe'
})
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.log(error);
  });

// 使用 async/await
async function createUser() {
  try {
    const response = await axios.post('/user', {
      firstName: 'John',
      lastName: 'Doe'
    });
    console.log(response.data);
  } catch (error) {
    console.log(error);
  }
}

其他 HTTP 方法

// PUT 请求
axios.put('/user/12345', {
  firstName: 'Jane',
  lastName: 'Smith'
});

// DELETE 请求
axios.delete('/user/12345');

// PATCH 请求
axios.patch('/user/12345', {
  firstName: 'Jane'
});

// HEAD 请求
axios.head('/user/12345');

// OPTIONS 请求
axios.options('/user/12345');

响应结构

axios.get('/user/12345')
  .then(function (response) {
    // 响应数据
    console.log(response.data);
    // 状态码
    console.log(response.status);
    // 状态文本
    console.log(response.statusText);
    // 响应头
    console.log(response.headers);
    // 配置
    console.log(response.config);
  });

高级特性

拦截器

// 添加请求拦截器
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    // 例如添加认证令牌
    config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
    return config;
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
axios.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response;
  },
  function (error) {
    // 对响应错误做点什么
    if (error.response) {
      // 服务器返回错误状态码
      switch (error.response.status) {
        case 401:
          // 未授权,重定向到登录页
          window.location.href = '/login';
          break;
        case 403:
          // 禁止访问
          console.log('Access forbidden');
          break;
        case 404:
          // 资源不存在
          console.log('Resource not found');
          break;
        case 500:
          // 服务器错误
          console.log('Server error');
          break;
        default:
          console.log('An error occurred');
      }
    } else if (error.request) {
      // 请求已发送但没有收到响应
      console.log('No response received');
    } else {
      // 请求配置出错
      console.log('Error:', error.message);
    }
    return Promise.reject(error);
  }
);

// 移除拦截器
const myInterceptor = axios.interceptors.request.use(function () { /*...*/ });
axios.interceptors.request.eject(myInterceptor);

实例和配置

// 创建实例
const instance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 1000,
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token'
  }
});

// 实例方法
instance.get('/user')
  .then(function (response) {
    console.log(response);
  });

// 全局配置
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = 'Bearer token';
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

取消请求

// 使用 CancelToken.source()
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user', {
  cancelToken: source.token
})
  .catch(function (thrown) {
    if (axios.isCancel(thrown)) {
      console.log('Request canceled', thrown.message);
    } else {
      // 处理错误
    }
  });

// 取消请求
source.cancel('Operation canceled by the user.');

// 使用执行器函数
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user', {
  cancelToken: new CancelToken(function executor(c) {
    // 执行器函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// 取消请求
cancel();

并发请求

// 使用 Promise.all
function getUserData() {
  return axios.get('/user/12345');
}

function getPosts() {
  return axios.get('/user/12345/posts');
}

Promise.all([getUserData(), getPosts()])
  .then(function (responses) {
    const userData = responses[0].data;
    const posts = responses[1].data;
    console.log(userData, posts);
  });

// 使用 axios.all
axios.all([
  axios.get('/user/12345'),
  axios.get('/user/12345/posts')
])
  .then(axios.spread(function (userResponse, postsResponse) {
    const userData = userResponse.data;
    const posts = postsResponse.data;
    console.log(userData, posts);
  }));

错误处理

axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // 服务器返回错误状态码
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // 请求已发送但没有收到响应
      console.log(error.request);
    } else {
      // 请求配置出错
      console.log('Error:', error.message);
    }
    console.log(error.config);
  });

// 自定义错误处理
axios.get('/user/12345')
  .then(function (response) {
    if (response.data.error) {
      // 处理业务逻辑错误
      return Promise.reject(new Error(response.data.error.message));
    }
    return response;
  })
  .catch(function (error) {
    console.log('Error:', error.message);
  });

转换请求和响应

// 全局转换
axios.defaults.transformRequest = [function (data, headers) {
  // 对请求数据进行转换
  if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
    return Object.entries(data)
      .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
      .join('&');
  }
  return data;
}];

axios.defaults.transformResponse = [function (data) {
  // 对响应数据进行转换
  return data;
}];

// 实例级转换
const instance = axios.create({
  transformRequest: [function (data, headers) {
    // 对请求数据进行转换
    return data;
  }],
  transformResponse: [function (data) {
    // 对响应数据进行转换
    return data;
  }]
});

// 请求级转换
axios.get('/user/12345', {
  transformResponse: [function (data) {
    // 对响应数据进行转换
    return data;
  }]
});

实用场景

基本 API 调用

// 封装 API 调用
const api = {
  getUsers() {
    return axios.get('/users');
  },
  getUserById(id) {
    return axios.get(`/users/${id}`);
  },
  createUser(user) {
    return axios.post('/users', user);
  },
  updateUser(id, user) {
    return axios.put(`/users/${id}`, user);
  },
  deleteUser(id) {
    return axios.delete(`/users/${id}`);
  }
};

// 使用
api.getUsers()
  .then(response => {
    console.log(response.data);
  });

文件上传

// 上传单个文件
const formData = new FormData();
formData.append('file', fileInput.files[0]);

axios.post('/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
})
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

// 上传多个文件
const formData = new FormData();
for (let i = 0; i < fileInput.files.length; i++) {
  formData.append('files', fileInput.files[i]);
}

axios.post('/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
});

// 上传进度
axios.post('/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  },
  onUploadProgress: function (progressEvent) {
    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    console.log(percentCompleted);
  }
});

文件下载

// 下载文件
axios.get('/download', {
  responseType: 'blob'
})
  .then(function (response) {
    const url = window.URL.createObjectURL(new Blob([response.data]));
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', 'file.pdf');
    document.body.appendChild(link);
    link.click();
  });

// 下载大文件
axios.get('/download', {
  responseType: 'stream'
})
  .then(function (response) {
    response.data.pipe(fs.createWriteStream('file.pdf'));
  });

处理跨域请求

// 配置跨域请求
const instance = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json'
  },
  withCredentials: true // 允许携带凭证
});

// 发送跨域请求
instance.get('/user')
  .then(function (response) {
    console.log(response.data);
  });

最佳实践

  1. 创建实例:为不同的 API 端点创建不同的实例
  2. 使用拦截器:使用拦截器处理认证、日志记录等横切关注点
  3. 封装 API 调用:将 API 调用封装成函数或类,提高代码可维护性
  4. 错误处理:统一处理错误,提供友好的错误提示
  5. 取消请求:在组件卸载时取消未完成的请求,避免内存泄漏
  6. 使用 async/await:使用 async/await 使代码更简洁易读
  7. 设置合理的超时:为请求设置合理的超时时间
  8. 使用 TypeScript:使用 TypeScript 提供类型安全
  9. 测试:为 API 调用编写测试
  10. 文档:为 API 调用编写文档

常见问题与解决方案

1. CORS 错误

问题:跨域请求被浏览器阻止

解决方案

  • 在服务器端设置 CORS 头
  • 使用代理服务器
  • 在 Axios 实例中设置 withCredentials: true

2. 认证令牌管理

问题:如何管理认证令牌

解决方案

  • 使用请求拦截器自动添加认证令牌
  • 在响应拦截器中处理令牌过期
  • 将令牌存储在 localStorage 或 sessionStorage 中

3. 请求超时

问题:请求超时

解决方案

  • 设置合理的超时时间
  • 实现重试机制
  • 显示加载状态,提高用户体验

4. 取消请求

问题:如何取消未完成的请求

解决方案

  • 使用 CancelToken 取消请求
  • 在组件卸载时取消请求
  • 在用户操作导致不需要的请求时取消

5. 大数据传输

问题:传输大文件或大量数据时性能问题

解决方案

  • 使用流式传输
  • 实现分块上传/下载
  • 显示传输进度

6. 并发请求限制

问题:同时发送过多请求导致性能问题

解决方案

  • 实现请求队列
  • 限制并发请求数量
  • 使用缓存避免重复请求

与其他 HTTP 客户端的比较

Axios vs Fetch API

  • API 简洁性:Axios API 更简洁,使用 Promise
  • 浏览器兼容性:Axios 支持旧浏览器,Fetch API 只支持现代浏览器
  • 功能丰富度:Axios 提供更多功能,如拦截器、取消请求等
  • 错误处理:Axios 自动将 4xx/5xx 响应视为错误,Fetch API 只在网络错误时视为错误
  • 配置:Axios 支持全局配置,Fetch API 每次请求都需要配置

Axios vs jQuery.ajax

  • 依赖:Axios 无依赖,jQuery.ajax 依赖 jQuery
  • 体积:Axios 体积更小
  • 功能:两者功能相似,但 Axios API 更现代
  • 生态系统:Axios 生态系统更活跃
  • TypeScript 支持:Axios 有良好的 TypeScript 支持

Axios vs SuperAgent

  • API 风格:两者 API 风格相似
  • 功能:两者功能相似
  • 生态系统:Axios 生态系统更活跃
  • 社区支持:Axios 社区支持更好
  • TypeScript 支持:Axios 有更好的 TypeScript 支持

参考资源

  1. 官方文档https://axios-http.com/docs/intro
  2. GitHub 仓库https://github.com/axios/axios
  3. Axios 示例https://axios-http.com/docs/example
  4. 拦截器文档https://axios-http.com/docs/interceptors
  5. 错误处理文档https://axios-http.com/docs/handling_errors
  6. 取消请求文档https://axios-http.com/docs/cancellation
  7. TypeScript 支持https://axios-http.com/docs/typeScript_intro
« 上一篇 Lodash 教程 - 现代化的 JavaScript 实用工具库 下一篇 » Day.js 教程 - 轻量级的 JavaScript 日期处理库