# Axios 常见应用场景

URL: https://caijiao.org/axios/intro/use-cases
Source: docs/axios/intro/use-cases.md
Description: Axios 在现代 Web 开发中应用广泛，本文详细介绍其在 RESTful API 调用、表单提交、文件上传下载、认证授权、数据聚合等实际开发场景中的应用方法与最佳实践。

Axios 作为一个功能强大的 HTTP 客户端库，在实际项目中有多种应用场景。本文将详细介绍 Axios 的常见使用场景，并提供实用的代码示例和最佳实践。

## 1. RESTful API 调用

RESTful API 是现代 Web 应用中最常见的数据交互方式，Axios 提供了简洁的方法来调用各种 RESTful 接口。

### 基础 CRUD 操作

```javascript
// 创建资源（CREATE）
async function createUser(userData) {
  try {
    const response = await axios.post("/api/users", userData);
    return response.data;
  } catch (error) {
    console.error("创建用户失败:", error);
    throw error;
  }
}

// 获取资源列表（READ）
async function getUsers() {
  try {
    const response = await axios.get("/api/users", {
      params: {
        page: 1,
        limit: 10,
      },
    });
    return response.data;
  } catch (error) {
    console.error("获取用户列表失败:", error);
    throw error;
  }
}

// 更新资源（UPDATE）
async function updateUser(userId, userData) {
  try {
    const response = await axios.put(`/api/users/${userId}`, userData);
    return response.data;
  } catch (error) {
    console.error("更新用户失败:", error);
    throw error;
  }
}

// 删除资源（DELETE）
async function deleteUser(userId) {
  try {
    await axios.delete(`/api/users/${userId}`);
    return true;
  } catch (error) {
    console.error("删除用户失败:", error);
    throw error;
  }
}
```

### 批量操作

```javascript
// 批量删除用户
async function deleteMultipleUsers(userIds) {
  try {
    await axios.delete("/api/users/batch", {
      data: { userIds }, // 将数据放在请求体中
    });
    return true;
  } catch (error) {
    console.error("批量删除用户失败:", error);
    throw error;
  }
}
```

## 2. 表单提交与数据验证

在 Web 应用中，表单提交通常需要与后端 API 交互。Axios 可以轻松处理表单数据的提交。

### 基本表单提交

```javascript
// HTML 表单
// <form id="login-form">
//   <input type="email" name="email">
//   <input type="password" name="password">
//   <button type="submit">登录</button>
// </form>

// 表单提交处理
document
  .getElementById("login-form")
  .addEventListener("submit", async (event) => {
    event.preventDefault();

    const formData = new FormData(event.target);
    const userData = Object.fromEntries(formData.entries());

    try {
      const response = await axios.post("/api/login", userData);
      // 处理登录成功
      localStorage.setItem("token", response.data.token);
      window.location.href = "/dashboard";
    } catch (error) {
      // 显示错误信息
      const errorElement = document.createElement("div");
      errorElement.className = "error-message";
      errorElement.textContent = error.response?.data?.message || "登录失败";
      event.target.appendChild(errorElement);
    }
  });
```

### 表单验证与错误处理

```javascript
async function submitForm(formData) {
  // 前端验证
  if (!formData.email.includes("@")) {
    throw new Error("请输入有效的邮箱地址");
  }

  try {
    const response = await axios.post("/api/submit", formData, {
      // 配置请求头
      headers: {
        "Content-Type": "application/json",
      },
      // 设置超时
      timeout: 5000,
    });
    return response.data;
  } catch (error) {
    // 处理不同类型的错误
    if (error.response) {
      // 后端返回的验证错误
      if (error.response.status === 422) {
        const validationErrors = error.response.data.errors;
        // 格式化错误信息
        const errorMessages = Object.entries(validationErrors)
          .map(([field, msgs]) => `${field}: ${msgs.join(", ")}`)
          .join("\n");
        throw new Error(`验证失败:\n${errorMessages}`);
      }
    }
    throw error;
  }
}
```

## 3. 文件上传与下载

Axios 可以处理文件上传和下载，支持进度监控等高级功能。

### 单文件上传

```javascript
// HTML
// <input type="file" id="file-upload">

const fileInput = document.getElementById("file-upload");

fileInput.addEventListener("change", async () => {
  const file = fileInput.files[0];
  const formData = new FormData();
  formData.append("file", file);
  formData.append("description", "用户上传的文件");

  try {
    const response = await axios.post("/api/upload", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
      },
      // 上传进度监控
      onUploadProgress: (progressEvent) => {
        const percentCompleted = Math.round(
          (progressEvent.loaded * 100) / progressEvent.total
        );
        console.log(`上传进度: ${percentCompleted}%`);
      },
    });

    console.log("上传成功:", response.data);
  } catch (error) {
    console.error("上传失败:", error);
  }
});
```

### 文件下载

```javascript
async function downloadFile(fileId, fileName) {
  try {
    const response = await axios.get(`/api/files/${fileId}`, {
      responseType: "blob", // 重要：设置响应类型为 blob
    });

    // 创建下载链接
    const url = window.URL.createObjectURL(new Blob([response.data]));
    const link = document.createElement("a");
    link.href = url;
    link.setAttribute("download", fileName);
    document.body.appendChild(link);
    link.click();

    // 清理
    document.body.removeChild(link);
    window.URL.revokeObjectURL(url);
  } catch (error) {
    console.error("文件下载失败:", error);
  }
}
```

### 大文件分片上传

```javascript
async function uploadLargeFile(file) {
  const chunkSize = 1024 * 1024; // 1MB 分片
  const totalChunks = Math.ceil(file.size / chunkSize);
  const fileId = generateUniqueId(); // 生成唯一文件ID

  for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
    const start = chunkIndex * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append("file", chunk);
    formData.append("fileId", fileId);
    formData.append("chunkIndex", chunkIndex);
    formData.append("totalChunks", totalChunks);
    formData.append("fileName", file.name);

    try {
      await axios.post("/api/upload/chunk", formData, {
        onUploadProgress: (progressEvent) => {
          const chunkProgress = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          const overallProgress = Math.round(
            ((chunkIndex * chunkSize + progressEvent.loaded) * 100) / file.size
          );
          console.log(
            `分片 ${chunkIndex + 1}/${totalChunks} 进度: ${chunkProgress}%`
          );
          console.log(`整体进度: ${overallProgress}%`);
        },
      });
    } catch (error) {
      console.error(`分片 ${chunkIndex} 上传失败:`, error);
      // 可以实现重试逻辑
    }
  }

  // 通知服务器所有分片上传完成
  try {
    await axios.post("/api/upload/complete", {
      fileId,
      fileName: file.name,
    });
    console.log("文件上传完成");
  } catch (error) {
    console.error("通知服务器完成失败:", error);
  }
}
```

## 4. 认证与授权

在现代 Web 应用中，用户认证和授权是常见需求，Axios 可以很好地处理 Token 管理和认证请求。

### JWT Token 管理

```javascript
// 创建带有认证配置的 Axios 实例
const authAxios = axios.create({
  baseURL: "/api",
});

// 请求拦截器 - 添加认证 Token
authAxios.interceptors.request.use((config) => {
  const token = localStorage.getItem("auth_token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截器 - 处理 Token 过期
authAxios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    // 如果是 401 错误且没有重试过
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        // 尝试使用刷新 Token 获取新的访问 Token
        const refreshToken = localStorage.getItem("refresh_token");
        const response = await axios.post("/api/auth/refresh", {
          refresh_token: refreshToken,
        });

        const { access_token, refresh_token } = response.data;
        localStorage.setItem("auth_token", access_token);
        localStorage.setItem("refresh_token", refresh_token);

        // 更新原始请求的 Authorization 头并重试
        authAxios.defaults.headers.common[
          "Authorization"
        ] = `Bearer ${access_token}`;
        originalRequest.headers.Authorization = `Bearer ${access_token}`;

        return authAxios(originalRequest);
      } catch (refreshError) {
        // 刷新 Token 失败，跳转到登录页
        localStorage.removeItem("auth_token");
        localStorage.removeItem("refresh_token");
        window.location.href = "/login";
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);
```

### 登录与注销流程

```javascript
// 登录功能
async function login(credentials) {
  try {
    const response = await axios.post("/api/auth/login", credentials);
    const { access_token, refresh_token, user } = response.data;

    // 存储认证信息
    localStorage.setItem("auth_token", access_token);
    localStorage.setItem("refresh_token", refresh_token);
    localStorage.setItem("user", JSON.stringify(user));

    return { user };
  } catch (error) {
    throw error.response?.data || error;
  }
}

// 注销功能
async function logout() {
  try {
    // 向服务器发送注销请求
    await authAxios.post("/api/auth/logout");
  } catch (error) {
    console.error("注销请求失败，但仍会清理本地数据:", error);
  } finally {
    // 清理本地存储的认证信息
    localStorage.removeItem("auth_token");
    localStorage.removeItem("refresh_token");
    localStorage.removeItem("user");
  }
}
```

## 5. 数据聚合与批量请求

在复杂应用中，常常需要同时获取多个数据源的数据，Axios 提供了并发请求的支持。

### 并行请求多个 API

```javascript
async function fetchDashboardData() {
  try {
    // 并行发送多个请求
    const [usersResponse, postsResponse, statsResponse] = await Promise.all([
      axios.get("/api/users?limit=10"),
      axios.get("/api/posts?limit=5"),
      axios.get("/api/stats/dashboard"),
    ]);

    // 整合数据
    return {
      users: usersResponse.data,
      posts: postsResponse.data,
      stats: statsResponse.data,
    };
  } catch (error) {
    console.error("获取仪表板数据失败:", error);
    throw error;
  }
}
```

### 动态批量请求

```javascript
async function fetchMultipleResources(resourceIds, resourceType) {
  if (!resourceIds.length) return [];

  try {
    // 为每个 ID 创建一个请求
    const requests = resourceIds.map((id) =>
      axios.get(`/api/${resourceType}/${id}`)
    );

    // 等待所有请求完成
    const responses = await Promise.all(requests);

    // 提取数据
    return responses.map((response) => response.data);
  } catch (error) {
    console.error(`获取 ${resourceType} 资源失败:`, error);
    throw error;
  }
}

// 使用示例
const articleIds = [1, 2, 3, 4, 5];
const articles = await fetchMultipleResources(articleIds, "articles");
```

### 请求超时和重试

```javascript
// 创建带超时和重试功能的请求函数
async function requestWithRetry(config, retries = 3, delay = 1000) {
  try {
    const response = await axios({
      ...config,
      timeout: 5000, // 设置超时时间
    });
    return response;
  } catch (error) {
    if (retries > 0 && error.code === "ECONNABORTED") {
      // 如果超时且还有重试次数，延迟后重试
      await new Promise((resolve) => setTimeout(resolve, delay));
      console.log(`请求超时，${retries} 次重试剩余，延迟 ${delay}ms 后重试...`);
      return requestWithRetry(config, retries - 1, delay * 2); // 指数退避
    }
    throw error;
  }
}

// 使用示例
const data = await requestWithRetry({
  url: "/api/critical-data",
  method: "get",
});
```

## 6. 全局请求管理

在大型应用中，通常需要对所有请求进行统一管理，包括错误处理、加载状态、日志记录等。

### 创建全局请求管理器

```javascript
class ApiService {
  constructor() {
    this.loading = false;
    this.requestCount = 0;

    // 创建 Axios 实例
    this.axios = axios.create({
      baseURL: process.env.API_BASE_URL || "/api",
      timeout: 10000,
      headers: {
        "Content-Type": "application/json",
      },
    });

    this.setupInterceptors();
  }

  setupInterceptors() {
    // 请求拦截器
    this.axios.interceptors.request.use(
      (config) => {
        // 增加请求计数
        this.requestCount++;
        this.loading = true;

        // 可以在这里触发全局加载状态
        console.log("请求开始:", config.url);

        // 添加认证 token
        const token = localStorage.getItem("auth_token");
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }

        // 记录请求日志
        if (process.env.NODE_ENV === "development") {
          console.log("API Request:", {
            url: config.url,
            method: config.method,
            params: config.params,
            data: config.data,
          });
        }

        return config;
      },
      (error) => {
        this.handleRequestEnd();
        return Promise.reject(error);
      }
    );

    // 响应拦截器
    this.axios.interceptors.response.use(
      (response) => {
        this.handleRequestEnd();

        // 记录响应日志
        if (process.env.NODE_ENV === "development") {
          console.log("API Response:", {
            url: response.config.url,
            status: response.status,
            data: response.data,
          });
        }

        return response;
      },
      (error) => {
        this.handleRequestEnd();

        // 统一错误处理
        this.handleError(error);

        return Promise.reject(error);
      }
    );
  }

  handleRequestEnd() {
    this.requestCount--;
    if (this.requestCount <= 0) {
      this.requestCount = 0;
      this.loading = false;
      // 可以在这里关闭全局加载状态
      console.log("所有请求完成");
    }
  }

  handleError(error) {
    // 根据错误类型显示不同的错误信息
    if (error.response) {
      // 服务器返回错误状态码
      switch (error.response.status) {
        case 400:
          console.error("请求参数错误:", error.response.data.message);
          break;
        case 401:
          console.error("未授权，请重新登录");
          // 可以在这里跳转到登录页
          break;
        case 403:
          console.error("禁止访问");
          break;
        case 404:
          console.error("请求的资源不存在");
          break;
        case 500:
          console.error("服务器内部错误");
          break;
        default:
          console.error(`请求失败: ${error.response.status}`);
      }
    } else if (error.request) {
      // 请求已发出但没有收到响应
      console.error("网络错误，请检查您的网络连接");
    } else {
      // 请求配置出错
      console.error("请求配置错误:", error.message);
    }
  }

  // 提供各种请求方法
  get(url, config) {
    return this.axios.get(url, config);
  }

  post(url, data, config) {
    return this.axios.post(url, data, config);
  }

  put(url, data, config) {
    return this.axios.put(url, data, config);
  }

  delete(url, config) {
    return this.axios.delete(url, config);
  }

  patch(url, data, config) {
    return this.axios.patch(url, data, config);
  }

  // 并发请求
  all(promises) {
    return Promise.all(promises);
  }
}

// 创建单例实例
const apiService = new ApiService();

export default apiService;
```

## 7. WebSocket 结合使用

虽然 Axios 主要用于 HTTP 请求，但在实际项目中，它经常与 WebSocket 结合使用，处理不同类型的数据交互需求。

```javascript
class DataService {
  constructor() {
    this.api = apiService; // 使用上面创建的 ApiService 实例
    this.socket = null;
    this.listeners = new Map();
  }

  // 初始化 WebSocket 连接
  initWebSocket() {
    const token = localStorage.getItem("auth_token");
    this.socket = new WebSocket(`wss://api.example.com/ws?token=${token}`);

    this.socket.onopen = () => {
      console.log("WebSocket 连接已建立");
    };

    this.socket.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        const { type, payload } = data;

        // 触发对应的监听器
        if (this.listeners.has(type)) {
          this.listeners.get(type).forEach((callback) => callback(payload));
        }
      } catch (error) {
        console.error("解析 WebSocket 消息失败:", error);
      }
    };

    this.socket.onclose = () => {
      console.log("WebSocket 连接已关闭");
      // 可以实现重连逻辑
    };

    this.socket.onerror = (error) => {
      console.error("WebSocket 错误:", error);
    };
  }

  // 订阅事件
  subscribe(eventType, callback) {
    if (!this.listeners.has(eventType)) {
      this.listeners.set(eventType, new Set());
    }
    this.listeners.get(eventType).add(callback);

    // 返回取消订阅函数
    return () => {
      this.unsubscribe(eventType, callback);
    };
  }

  // 取消订阅
  unsubscribe(eventType, callback) {
    if (this.listeners.has(eventType)) {
      this.listeners.get(eventType).delete(callback);

      // 如果没有监听器了，清理
      if (this.listeners.get(eventType).size === 0) {
        this.listeners.delete(eventType);
      }
    }
  }

  // 发送 WebSocket 消息
  sendSocketMessage(type, payload) {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify({ type, payload }));
    } else {
      console.error("WebSocket 连接未建立或已关闭");
    }
  }

  // 结合 HTTP 请求和 WebSocket
  async fetchInitialData() {
    // 使用 Axios 获取初始数据
    const initialData = await this.api.get("/api/initial-data");

    // 初始化 WebSocket 以获取实时更新
    this.initWebSocket();

    return initialData.data;
  }
}

// 使用示例
const dataService = new DataService();

// 获取初始数据并设置实时更新
async function setupDataFlow() {
  const initialData = await dataService.fetchInitialData();
  updateUI(initialData);

  // 订阅实时更新
  const unsubscribe = dataService.subscribe("data_update", (updatedData) => {
    updateUI(updatedData);
  });

  // 组件卸载时取消订阅
  // unsubscribe();
}
```

## 总结

Axios 作为一个功能强大的 HTTP 客户端库，在现代 Web 开发中有广泛的应用场景。无论是简单的 API 调用、复杂的文件传输，还是全局的请求管理，Axios 都能提供简洁而强大的解决方案。

通过灵活运用 Axios 的各种特性，开发者可以构建更加健壮、高效和用户友好的 Web 应用。在实际项目中，建议根据具体需求，结合 Axios 的最佳实践，创建适合项目的 API 服务层，以提高代码的可维护性和可重用性。
