Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇

【Axios接口封装+前端规范】中后台项目实战:从请求参数到异常兜底,一站式搞定接口请求规范,避开团队协作混乱与维护噩梦!

📑 文章目录

  • 一、为什么要做「接口请求规范」?
  • 二、请求参数规范
    • [2.1 参数怎么传?GET / POST 怎么选?](#2.1 参数怎么传?GET / POST 怎么选?)
    • [2.2 统一封装请求参数](#2.2 统一封装请求参数)
    • [2.3 参数命名和格式约定](#2.3 参数命名和格式约定)
  • 三、响应处理规范
    • [3.1 响应格式要统一](#3.1 响应格式要统一)
    • [3.2 响应拦截器:统一剥一层](#3.2 响应拦截器:统一剥一层)
    • [3.3 不同后端约定的兼容写法](#3.3 不同后端约定的兼容写法)
  • 四、异常兜底规范
    • [4.1 常见异常来源](#4.1 常见异常来源)
    • [4.2 异常兜底要做什么?](#4.2 异常兜底要做什么?)
    • [4.3 完整异常处理示例](#4.3 完整异常处理示例)
    • [4.4 业务层:按需捕获](#4.4 业务层:按需捕获)
  • 五、完整实战示例
    • [5.1 完整的 request 封装](#5.1 完整的 request 封装)
    • [5.2 业务调用示例(Vue 组件)](#5.2 业务调用示例(Vue 组件))
  • 六、常见坑与最佳实践
  • 七、小结
  • [🔍 系列模块导航](#🔍 系列模块导航)
    • [📝 API 与异步请求规范](#📝 API 与异步请求规范)
    • [📚 系列总览](#📚 系列总览)

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。

一、为什么要做「接口请求规范」?

前端工程里,接口调用通常占了很大比重。如果没有统一规范,很容易出现:

  • 参数是 params 还是 query、用 GET 还是 POST 乱用;
  • 有的接口用 data.code === 0,有的用 data.success,判断逻辑分散;
  • 错误只 console.log 不处理,页面没有任何提示;
  • 重复逻辑散落各处,改一处要改很多地方。

做好「请求参数 + 响应处理 + 异常兜底」这三块,能让代码更清晰、更好维护。下面按这三块来拆解。


二、请求参数规范

2.1 参数怎么传?GET / POST 怎么选?

简单记法:

  • GET:查数据,参数放在 URL 上(query),无请求体;
  • POST:增、改、删,或参数多、敏感时,参数放在请求体(body)。

常见错误:用 GET 传大量或敏感参数。GET 会出现在地址栏和日志里,不安全。

js 复制代码
// ❌ 不推荐:GET 传复杂或敏感数据
axios.get('/api/user/login', {
  params: {
    username: 'admin',
    password: '123456',  // 明文出现在 URL 里
  }
})

// ✅ 推荐:登录用 POST,密码放 body
axios.post('/api/user/login', {
  username: 'admin',
  password: '123456',
})

[⬆ 返回目录](#⬆ 返回目录)

2.2 统一封装请求参数

把「参数处理」集中在一个地方,后续改参数格式会轻松很多。

js 复制代码
// utils/request.js

import axios from 'axios'

// 创建实例
const instance = axios.create({
  baseURL: '/api',
  timeout: 10000,
})

// 请求拦截器:统一处理参数
instance.interceptors.request.use(
  (config) => {
    // 1. 统一加 token
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }

    // 2. GET 请求:params 保持原样,Axios 会自动拼到 URL
    // 3. POST 请求:data 保持原样,Axios 会序列化到 body
    return config
  },
  (error) => Promise.reject(error)
)

export default instance

[⬆ 返回目录](#⬆ 返回目录)

2.3 参数命名和格式约定

建议和接口文档保持一致,例如:

  • 分页:pageNumpageSize
  • 时间:startTimeendTime(ISO 字符串或时间戳);
  • 布尔:isActiveenabled 等,统一用 true/false
js 复制代码
// ✅ 分页参数示例
const fetchUserList = (pageNum = 1, pageSize = 10) => {
  return request.get('/user/list', {
    params: {
      pageNum,
      pageSize,
      // 后端约定:0-全部 1-启用 2-禁用
      status: 1,
    }
  })
}

[⬆ 返回目录](#⬆ 返回目录)


三、响应处理规范

3.1 响应格式要统一

后端一般会约定类似结构:

json 复制代码
{
  "code": 0,
  "message": "success",
  "data": { ... }
}

前端用统一的判断逻辑,不要有的地方看 code,有的地方看 success

[⬆ 返回目录](#⬆ 返回目录)

3.2 响应拦截器:统一剥一层

在响应拦截器里统一处理 code,把业务数据直接返回给调用方,调用处只关心业务数据。

js 复制代码
// utils/request.js 响应拦截器

instance.interceptors.response.use(
  (response) => {
    const { code, message, data } = response.data

    // 业务成功
    if (code === 0 || code === 200) {
      return data
    }

    // 业务失败:如 401 未登录、403 无权限
    if (code === 401) {
      // 清 token、跳登录等
      localStorage.removeItem('token')
      window.location.href = '/login'
      return Promise.reject(new Error('未登录'))
    }

    if (code === 403) {
      return Promise.reject(new Error('无权限'))
    }

    // 其他业务错误
    return Promise.reject(new Error(message || '请求失败'))
  },
  (error) => {
    // 网络错误、超时等,交给后面的异常兜底
    return Promise.reject(error)
  }
)

这样在业务代码里,直接用 data

js 复制代码
// 业务代码中
const list = await request.get('/user/list')
// list 已经是 data 部分,不再需要 response.data.data
console.log(list)

[⬆ 返回目录](#⬆ 返回目录)

3.3 不同后端约定的兼容写法

有的后端用 code === 0 表示成功,有的用 code === 200success === true,可以集中配置:

js 复制代码
// config/api.js
export const SUCCESS_CODES = [0, 200]

// utils/request.js
if (SUCCESS_CODES.includes(code)) {
  return data
}

[⬆ 返回目录](#⬆ 返回目录)


四、异常兜底规范

4.1 常见异常来源

  1. 网络错误:断网、超时;
  2. HTTP 状态码:404、500;
  3. 业务错误:code !== 0,如参数错误、余额不足;
  4. 前端逻辑错误:如解析 JSON 失败。

[⬆ 返回目录](#⬆ 返回目录)

4.2 异常兜底要做什么?

  1. 统一提示用户(如 Toast、Message);
  2. 区分可重试 / 不可重试;
  3. 可选:上报错误,方便排查。

[⬆ 返回目录](#⬆ 返回目录)

4.3 完整异常处理示例

js 复制代码
// utils/request.js

import { Message } from 'element-ui'  // 或你的 UI 库

instance.interceptors.response.use(
  (response) => { /* 上面已有,省略 */ },
  (error) => {
    let message = '网络异常,请稍后重试'

    if (error.response) {
      // 有 response:服务器返回了错误
      const { status, data } = error.response
      switch (status) {
        case 400:
          message = data?.message || '请求参数错误'
          break
        case 401:
          message = '未登录或登录已过期'
          localStorage.removeItem('token')
          window.location.href = '/login'
          break
        case 403:
          message = '没有权限'
          break
        case 404:
          message = '请求的资源不存在'
          break
        case 500:
          message = '服务器错误,请稍后重试'
          break
        default:
          message = data?.message || `请求失败(${status})`
      }
    } else if (error.request) {
      // 有 request 但无 response:网络异常
      if (error.code === 'ECONNABORTED') {
        message = '请求超时,请检查网络'
      } else {
        message = '网络连接失败,请检查网络'
      }
    } else {
      message = error.message || '未知错误'
    }

    Message.error(message)
    return Promise.reject(error)
  }
)

[⬆ 返回目录](#⬆ 返回目录)

4.4 业务层:按需捕获

有时某个接口希望自己处理错误,不弹出全局提示,可以:

js 复制代码
// 1. 在接口配置里标记「静默」
// 2. 或在业务里用 try-catch 包一层

try {
  const res = await request.post('/user/delete', { id: 1 })
  Message.success('删除成功')
  // 刷新列表等
} catch (e) {
  // 这里已经不会弹全局提示,因为上面拦截器 reject 后,这里会 catch
  // 但拦截器里已经 Message.error 了,如果不想重复弹,可以:
  // 方案 A:在 error 上挂一个标记 skipGlobalError
  // 方案 B:这个接口单独用原始 axios,不走拦截器
}

更推荐做法:在请求配置中加 skipGlobalError,拦截器里判断:

js 复制代码
// 发起请求时
request.post('/user/delete', { id: 1 }, { skipGlobalError: true })

// 响应拦截器 error 中
if (!error.config?.skipGlobalError) {
  Message.error(message)
}

[⬆ 返回目录](#⬆ 返回目录)


五、完整实战示例

5.1 完整的 request 封装

js 复制代码
// utils/request.js

import axios from 'axios'
import { Message } from 'element-ui'

const instance = axios.create({
  baseURL: process.env.VUE_APP_BASE_API || '/api',
  timeout: 10000,
})

// 请求拦截
instance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// 响应拦截
instance.interceptors.response.use(
  (response) => {
    const { code, message, data } = response.data

    if (code === 0 || code === 200) {
      return data
    }

    if (code === 401) {
      localStorage.removeItem('token')
      window.location.href = '/login'
      return Promise.reject(new Error('未登录'))
    }

    const err = new Error(message || '请求失败')
    err.code = code
    return Promise.reject(err)
  },
  (error) => {
    const skipGlobalError = error.config?.skipGlobalError
    let msg = '网络异常,请稍后重试'

    if (error.response) {
      const { status, data } = error.response
      msg = data?.message || ({
        400: '请求参数错误',
        401: '未登录或登录已过期',
        403: '没有权限',
        404: '资源不存在',
        500: '服务器错误',
      })[status] || `请求失败(${status})`

      if (status === 401) {
        localStorage.removeItem('token')
        window.location.href = '/login'
      }
    } else if (error.request) {
      msg = error.code === 'ECONNABORTED' ? '请求超时' : '网络连接失败'
    } else {
      msg = error.message || '未知错误'
    }

    if (!skipGlobalError) {
      Message.error(msg)
    }
    return Promise.reject(error)
  }
)

export default instance

[⬆ 返回目录](#⬆ 返回目录)

5.2 业务调用示例(Vue 组件)

html 复制代码
<template>
  <div>
    <el-button @click="loadList" :loading="loading">加载列表</el-button>
    <el-table :data="list">
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="phone" label="手机" />
    </el-table>
  </div>
</template>

<script>
import request from '@/utils/request'

export default {
  data() {
    return {
      list: [],
      loading: false,
      pageNum: 1,
      pageSize: 10,
    }
  },
  created() {
    this.loadList()
  },
  methods: {
    async loadList() {
      this.loading = true
      try {
        // 直接拿到 data,结构清晰
        const res = await request.get('/user/list', {
          params: {
            pageNum: this.pageNum,
            pageSize: this.pageSize,
          }
        })
        this.list = res.list || res
      } catch (e) {
        // 拦截器已弹错,这里可做额外逻辑(如重试、记录)
        console.error('加载列表失败', e)
      } finally {
        this.loading = false
      }
    },
  },
}
</script>

[⬆ 返回目录](#⬆ 返回目录)


六、常见坑与最佳实践

场景 建议
重复请求 快速连点造成重复提交 按钮加 loading,或防抖/节流
Token 过期 多个请求同时 401,多次跳登录 加请求队列,收到 401 后统一处理
取消请求 切换页面前请求未结束,数据被旧请求覆盖 AbortController 或 axios 的 cancelToken 取消
文件上传 用 JSON,大文件慢或失败 FormDataContent-Type 交给浏览器
超时 默认超时过长 按接口类型设 timeout(列表 10s,上传 60s 等)

[⬆ 返回目录](#⬆ 返回目录)

七、小结

  • 请求参数:GET 查、POST 改/增/删;参数统一在拦截器处理;命名和格式与后端约定一致。
  • 响应处理 :在响应拦截器统一解析 code,成功时直接返回 data,失败统一 reject
  • 异常兜底 :区分网络错误、HTTP 错误、业务错误;统一提示;支持 skipGlobalError 做静默处理。

按这三块把接口层规范好,业务代码会清爽很多,后续维护和排查问题都会更轻松。

[⬆ 返回目录](#⬆ 返回目录)


🔍 系列模块导航

📝 API 与异步请求规范

一、《Axios 统一封装实战:拦截器配置 + baseURL 优化 + 接口规范,避坑重复代码|API 与异步请求规范篇》

二、《Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇》
三、《Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇》
四、《前端实战:Excel 导入导出规范(命名 + 校验 + 错误处理 + 统一交互)|API 与异步请求规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

📚 系列总览

前端体系化学习完全体:基础 → 规范 → 架构 → 大厂面试

四套系列、百余篇高质量实战文,从入门到进阶,一站式补齐前端核心能力

每个系列完结后,都会整理成一篇完整导航文并附上直达链接,方便大家按顺序、体系化学习。

全套内容持续更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
abigale032 小时前
【浏览器 API / 网络请求 / 文件处理】前端文件上传全流程:从基础上传到断点续传
前端·typescript·文件上传·vue cli
子兮曰2 小时前
Bun v1.3.11 官方更新全整理:新增功能、关键修复与升级验证
javascript·node.js·bun
Setsuna_F_Seiei2 小时前
AI 对话应用之页面滚动交互的实现
前端·javascript·ai编程
xuxie992 小时前
N11 ARM-irq
java·开发语言
新缸中之脑3 小时前
追踪来自Agent的Web 流量
前端
wefly20173 小时前
从使用到原理,深度解析m3u8live.cn—— 基于 HLS.js 的 M3U8 在线播放器实现
java·开发语言·前端·javascript·ecmascript·php·m3u8
luanma1509804 小时前
PHP vs C++:编程语言终极对决
开发语言·c++·php
寂静or沉默4 小时前
2026最新Java岗位从P5-P7的成长面试进阶资源分享!
java·开发语言·面试
英俊潇洒美少年4 小时前
vue如何实现react useDeferredvalue和useTransition的效果
前端·vue.js·react.js