盐汽水的海洋

JavaScript 异步请求 / Axios

avatar

盐汽水

🎯 目标:2-3小时掌握 Axios 核心用法,达到实战水平


📑 目录

  1. 核心概念
  2. 必须掌握的 API
  3. 典型应用场景与设计取舍
  4. 最小可运行示例
  5. 常见坑与调试技巧
  6. 分层练习清单
  7. 自测清单与闭卷题

1. 核心概念

1.1 什么是 Axios?

  • 定义:基于 Promise 的 HTTP 客户端,可在浏览器和 Node.js 中使用。比原生 fetch 更易用,自动转换 JSON。
  • 为什么用它:统一的错误处理、请求/响应拦截器、取消请求、超时控制、防 XSRF 等开箱即用的功能。

1.2 Promise 基础(前置知识)

  • Promise 三态:pending(进行中)→ fulfilled(成功)或 rejected(失败)。通过 .then() 处理成功,.catch() 处理失败。
  • async/await:Promise 的语法糖,让异步代码看起来像同步代码。await 只能在 async 函数内使用。

1.3 HTTP 请求核心要素

  • 方法:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)、PATCH(部分更新)。
  • 状态码:2xx(成功)、4xx(客户端错误,如 404)、5xx(服务器错误,如 500)。

2. 必须掌握的 API

2.1 基础请求方法

axios.get(url, config)        // GET 请求,参数通过 config.params 传递
axios.post(url, data, config) // POST 请求,data 是请求体
axios.put(url, data, config)  // PUT 全量更新
axios.delete(url, config)     // DELETE 删除

2.2 请求配置对象(config)

{
  url: '/api/users',           // 请求路径
  method: 'get',               // 默认 GET
  params: { id: 1 },           // URL 查询参数 (?id=1)
  data: { name: 'John' },      // 请求体(POST/PUT/PATCH)
  headers: { 'X-Token': 'abc' }, // 自定义请求头
  timeout: 5000,               // 超时时间(毫秒),超时自动取消请求
  responseType: 'json'         // 响应数据类型(默认 json)
}

2.3 响应对象结构

{
  data: {},           // 服务器返回的数据
  status: 200,        // HTTP 状态码
  statusText: 'OK',   // 状态文本
  headers: {},        // 响应头
  config: {},         // 请求配置
  request: {}         // 原始请求对象
}

2.4 创建实例

const api = axios.create({
  baseURL: 'https://api.example.com', // 基础 URL,所有请求自动拼接
  timeout: 10000,                     // 全局超时
  headers: { 'Content-Type': 'application/json' }
});

2.5 拦截器(Interceptors)

// 请求拦截器:发送前修改配置(如添加 token)
api.interceptors.request.use(
  config => { config.headers.Authorization = 'Bearer token'; return config; },
  error => Promise.reject(error)
);

// 响应拦截器:统一处理响应或错误(如 token 过期跳转登录)
api.interceptors.response.use(
  response => response.data, // 直接返回 data,简化调用
  error => { if(error.response?.status === 401) { /*跳转登录*/ } return Promise.reject(error); }
);

3. 典型应用场景与设计取舍

场景 方案 取舍理由
简单页面(几个请求) 直接用 axios.get/post 无需封装,代码直观
中大型项目 创建实例 + 拦截器 + API 模块化 统一配置(baseURL/token),便于维护
并发请求(同时获取多个资源) Promise.all([req1, req2]) 全部成功才处理,一个失败全失败
竞态问题(快速切换导致旧请求覆盖新请求) AbortController 取消旧请求 确保只处理最新请求的结果
轮询(定时刷新数据) setInterval + axios 简单场景可用,复杂的用 WebSocket
文件上传 FormData + axios.post 支持进度监听(onUploadProgress)

设计建议:

  1. 统一错误处理放在响应拦截器,避免每个请求都写 try-catch
  2. 将 API 按模块分文件(如 userApi.jsproductApi.js),导出函数而非裸请求。

4. 最小可运行示例

4.1 环境准备

# 方式1:CDN(直接在 HTML 中使用)
# 在 <head> 中添加:
# <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

# 方式2:npm 安装(推荐,用于项目)
npm install axios

4.2 完整示例(HTML + JavaScript)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Axios 实战示例</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <h1>用户管理系统</h1>

  <!-- 获取用户列表 -->
  <button onclick="getUsers()">获取用户列表</button>
  <ul id="userList"></ul>

  <!-- 创建新用户 -->
  <h3>创建用户</h3>
  <input id="userName" placeholder="输入用户名">
  <button onclick="createUser()">创建</button>

  <script>
    // ==================== 1. 创建 Axios 实例 ====================
    const api = axios.create({
      baseURL: 'https://jsonplaceholder.typicode.com', // 免费测试 API
      timeout: 5000,
      headers: {
        'Content-Type': 'application/json'
      }
    });

    // ==================== 2. 添加请求拦截器 ====================
    api.interceptors.request.use(
      config => {
        console.log('📤 发送请求:', config.method.toUpperCase(), config.url);
        // 实际项目中在这里添加 token
        // config.headers.Authorization = 'Bearer ' + localStorage.getItem('token');
        return config;
      },
      error => {
        console.error('❌ 请求错误:', error);
        return Promise.reject(error);
      }
    );

    // ==================== 3. 添加响应拦截器 ====================
    api.interceptors.response.use(
      response => {
        console.log('✅ 响应成功:', response.status, response.data);
        return response.data; // 直接返回数据部分,简化后续调用
      },
      error => {
        // 统一错误处理
        if (error.response) {
          // 服务器返回了错误状态码
          console.error(`❌ 服务器错误 ${error.response.status}:`, error.response.data);
          alert(`请求失败: ${error.response.status} - ${error.response.statusText}`);
        } else if (error.request) {
          // 请求已发送但没收到响应(网络问题)
          console.error('❌ 网络错误:', error.request);
          alert('网络连接失败,请检查网络');
        } else {
          // 请求配置出错
          console.error('❌ 请求配置错误:', error.message);
        }
        return Promise.reject(error);
      }
    );

    // ==================== 4. 封装 API 函数 ====================
    // GET 请求:获取用户列表
    async function getUsers() {
      try {
        // 方式1:async/await(推荐,代码更清晰)
        const users = await api.get('/users', {
          params: { _limit: 5 } // 限制返回5条数据
        });

        // 渲染到页面
        const userList = document.getElementById('userList');
        userList.innerHTML = users.map(user => 
          `<li>${user.id}. ${user.name} (${user.email})</li>`
        ).join('');

      } catch (error) {
        console.error('获取用户失败:', error);
        // 错误已在拦截器中处理,这里可以做额外的业务逻辑
      }
    }

    // POST 请求:创建用户
    async function createUser() {
      const userName = document.getElementById('userName').value;
      if (!userName) {
        alert('请输入用户名');
        return;
      }

      try {
        const newUser = await api.post('/users', {
          name: userName,
          email: `${userName}@example.com`,
          username: userName
        });

        alert(`✅ 创建成功! 用户ID: ${newUser.id}`);
        document.getElementById('userName').value = ''; // 清空输入框

      } catch (error) {
        console.error('创建用户失败:', error);
      }
    }

    // ==================== 5. 其他常用示例 ====================
    // 并发请求
    async function getConcurrentData() {
      try {
        const [users, posts] = await Promise.all([
          api.get('/users?_limit=3'),
          api.get('/posts?_limit=3')
        ]);
        console.log('用户:', users);
        console.log('文章:', posts);
      } catch (error) {
        console.error('并发请求失败:', error);
      }
    }

    // 取消请求(防止重复点击)
    let cancelTokenSource = null;
    async function searchWithCancel(keyword) {
      // 取消上一次未完成的请求
      if (cancelTokenSource) {
        cancelTokenSource.cancel('新搜索开始,取消旧请求');
      }

      cancelTokenSource = axios.CancelToken.source();

      try {
        const results = await api.get('/users', {
          params: { q: keyword },
          cancelToken: cancelTokenSource.token
        });
        console.log('搜索结果:', results);
      } catch (error) {
        if (axios.isCancel(error)) {
          console.log('请求已取消:', error.message);
        } else {
          console.error('搜索失败:', error);
        }
      }
    }

    // ==================== 6. 演示:页面加载时自动获取数据 ====================
    window.onload = () => {
      console.log('🚀 页面加载完成,可点击按钮测试');
      // 自动加载用户列表
      // getUsers();
    };
  </script>
</body>
</html>

4.3 运行步骤

  1. 保存上述代码axios-demo.html
  2. 双击打开该文件(或用 Live Server 启动)
  3. 点击"获取用户列表"按钮,查看控制台日志和页面渲染
  4. 输入用户名,点击"创建"按钮测试 POST 请求
  5. 打开浏览器开发者工具(F12)→ Network 标签,观察请求详情

5. 常见坑与调试技巧

5.1 常见错误

错误现象 原因 解决方案
CORS 跨域错误 浏览器安全策略,前端不同源访问后端 后端配置 CORS 头(Access-Control-Allow-Origin),或用代理
401 Unauthorized token 过期或未传递 在请求拦截器中添加 Authorization
超时错误 网络慢或 timeout 设置太短 增加 timeout 值,或检查网络
数据获取失败但不报错 忘记 returnawait 确保异步函数用 await,箭头函数记得 return
Cannot read property 'data' of undefined 直接访问 response.data 但响应失败 ?. 可选链:response?.data,或在拦截器统一处理

5.2 调试技巧

  1. 查看完整请求/响应

    axios.get('/api/users').then(res => {
     console.log('完整响应对象:', res);
     console.log('请求配置:', res.config);
     console.log('响应头:', res.headers);
    });
  2. 使用浏览器 Network 面板

    • 查看请求 URL、方法、状态码
    • 检查 Request Headers(是否带 token)
    • 查看 Response 原始数据(是否符合预期)
  3. 拦截器打日志

    api.interceptors.request.use(config => {
     console.log('📍 请求配置:', JSON.stringify(config, null, 2));
     return config;
    });
  4. 错误分类处理

    catch(error => {
     if (error.response) {
       // 服务器返回错误(4xx/5xx)
       console.log('状态码:', error.response.status);
       console.log('错误数据:', error.response.data);
     } else if (error.request) {
       // 请求发出但无响应(网络问题)
       console.log('无响应,检查网络');
     } else {
       // 请求配置错误
       console.log('配置错误:', error.message);
     }
    })

6. 分层练习清单

🌱 入门级(必做)

  1. 基础 CRUD 操作

    • 用 JSONPlaceholder API 完成:获取文章列表(GET)、创建文章(POST)、更新文章(PUT)
    • 要求:用 async/await,在页面上显示结果
  2. 配置超时与错误处理

    • 设置 3 秒超时,故意请求一个慢接口(或不存在的 URL)
    • 在 catch 中区分超时错误和 404 错误,弹出不同提示
  3. URL 参数传递

    • 实现搜索功能:输入关键词,用 params 传递给 /users?name=xxx
    • 打印最终请求的完整 URL

🔨 巩固级(强化理解)

  1. 创建 Axios 实例

    • 创建一个实例,设置 baseURL、默认 headers
    • 封装 3 个 API 函数:getUserList()getUserById(id)deleteUser(id)
  2. 请求/响应拦截器

    • 请求拦截器:自动给所有请求添加时间戳参数(_t=Date.now())
    • 响应拦截器:如果 response.data.code !== 200,自动 reject 并显示错误信息
  3. 并发请求处理

    • 同时请求用户列表和文章列表,全部成功后合并渲染到页面
    • Promise.all() 实现,处理其中一个失败的情况

🚀 进阶级(实战场景)

  1. 请求取消与防抖

    • 实现搜索框:输入时实时搜索,快速输入时取消旧请求,只保留最新请求
    • 结合 AbortControllerCancelToken
  2. 文件上传与进度

    • <input type="file"> 选择图片,通过 FormData 上传
    • 监听 onUploadProgress,显示上传百分比(用进度条或文本)
    • 测试 API:可用 https://httpbin.org/post
  3. Token 刷新机制

    • 模拟场景:token 有效期 5 秒,过期后自动刷新 token 并重试原请求
    • 响应拦截器检测 401,调用刷新接口,更新 token 后重发请求

7. 自测清单与闭卷题

📋 自测清单(每项打✅表示掌握)

  • [ ] 能解释 Promise、async/await 的作用和区别
  • [ ] 会用 axios.get/post 发送基本请求
  • [ ] 理解 params(查询参数)和 data(请求体)的区别
  • [ ] 知道如何配置 timeoutheadersbaseURL
  • [ ] 能读懂响应对象结构,知道从哪里取数据(response.data)
  • [ ] 会创建 Axios 实例,理解实例的好处
  • [ ] 能写请求拦截器(如添加 token)
  • [ ] 能写响应拦截器(如统一错误处理)
  • [ ] 会用 Promise.all() 处理并发请求
  • [ ] 知道如何取消请求(CancelTokenAbortController)
  • [ ] 会在浏览器 Network 面板调试请求
  • [ ] 能区分三类错误:error.responseerror.request、配置错误

📝 闭卷题(30分钟内完成)

第1题:代码分析(10分)

以下代码有什么问题?如何修复?

axios.get('https://api.example.com/users')
  .then(response => {
    console.log(response);
    return response.data.map(user => user.name);
  });

const userNames = getUserNames(); // 想获取用户名数组
console.log(userNames); // 期望打印 ['Alice', 'Bob']

标准答案:

问题:
1. axios.get 返回 Promise,但没有 return,导致 getUserNames 返回 undefined
2. 即使 return 了,外部直接赋值也是 undefined,因为 Promise 是异步的

修复方案:
// 方式1:async/await
async function getUserNames() {
  const response = await axios.get('https://api.example.com/users');
  return response.data.map(user => user.name);
}
// 使用时也要 await
const userNames = await getUserNames();

// 方式2:返回 Promise
function getUserNames() {
  return axios.get('https://api.example.com/users')
    .then(response => response.data.map(user => user.name));
}
getUserNames().then(names => console.log(names));

第2题:实战应用(15分)

实现一个用户登录功能,要求:

  1. 调用 POST /api/login,传递 { username, password }
  2. 登录成功后,将返回的 token 存到 localStorage
  3. 创建一个 Axios 实例,自动给所有请求添加 token(从 localStorage 读取)
  4. 如果响应状态码是 401,清除 token 并跳转到登录页

写出完整代码(可以用伪代码表示跳转逻辑)。

标准答案:

// 1. 创建 Axios 实例
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000
});

// 2. 请求拦截器:自动添加 token
api.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 3. 响应拦截器:处理 401
api.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login'; // 或用路由跳转
    }
    return Promise.reject(error);
  }
);

// 4. 登录函数
async function login(username, password) {
  try {
    const response = await axios.post('https://api.example.com/api/login', {
      username,
      password
    });

    // 保存 token
    localStorage.setItem('token', response.data.token);
    console.log('登录成功');
    return true;

  } catch (error) {
    console.error('登录失败:', error);
    return false;
  }
}

// 5. 使用示例
login('admin', '123456').then(success => {
  if (success) {
    // 之后用 api 发送的请求都会自动带 token
    api.get('/user/profile').then(data => console.log(data));
  }
});

第3题:场景题(5分)

你在开发搜索功能,用户在输入框快速输入时,会连续发送多个请求。如果第1个请求很慢,第5个请求很快返回,就会导致旧数据覆盖新数据

这种问题叫什么?有哪两种解决方案?(简要说明思路即可)

标准答案:

问题名称:竞态条件(Race Condition)

解决方案:
1. 取消旧请求:
   - 每次发新请求前,用 AbortController 或 CancelToken 取消上一个未完成的请求
   - 确保只有最新的请求会返回结果

2. 标记请求序号:
   - 给每个请求分配一个递增的 ID
   - 响应返回时,只处理 ID 最大的那个请求的数据
   - 需要维护一个全局变量记录最新的请求 ID

推荐方案1,代码更简洁,浏览器原生支持。

📚 补充资源


✅ 学习检查点

完成以下检查,确认你已掌握:

  1. ✅ 能独立用 Axios 完成 GET/POST/PUT/DELETE 请求
  2. ✅ 会创建实例并配置拦截器,能处理 token 和错误
  3. ✅ 理解异步请求的本质,能用 async/await 优雅处理
  4. ✅ 知道常见坑(CORS、401、超时)的排查方法
  5. ✅ 完成至少 6 道练习题,闭卷题得分 ≥ 24/30

恭喜!你已具备 Axios 实战能力,可以在项目中独立开发 API 交互模块了! 🎉

闽ICP备2021014815号-2
powered by emlog sitemap