
做前端开发时,和后端接口打交道是家常便饭,axios 作为常用的 HTTP 客户端,把它封装成 api service 能让接口管理更规范、开发效率更高,但不少同学刚开始会犯愁:axios api service 到底咋搭建?怎么解决重复代码、错误处理这些问题?接下来用问答的方式,把 axios api service 从基础到实战的知识点拆明白,帮你把接口层理顺。
简单说,axios api service 是把 axios 的请求逻辑(像请求拦截、响应处理、错误捕获这些)做统一封装,再把每个接口的请求细节(url、参数、方法)单独管理的一套“接口工具集”。
直接用 axios 发请求的话,每个组件里可能都要写 axios.get('/xxx') 这类代码,一旦后端改了接口地址、请求头,就得满项目找地方改;而封装成 api service 后,所有接口配置集中在一个地方,拦截器统一处理鉴权、loading,甚至不同环境的 baseURL 切换也能一键搞定,举个例子:直接写请求时,登录接口可能在多个页面重复写 axios.post('/login', data);但用 api service 后,会封装成 export function login(data) { return request.post('/login', data) },其他地方只要导入 login 函数调用就行,后期改接口路径只需要改 service 里的配置,维护起来省心多了。
怎么从零开始搭建一套 axios api service?
分步骤来讲,核心是“创建请求实例 + 配置拦截器 + 接口模块化管理”:
创建 axios 实例(统一配置基础参数)
新建 request.js 文件,用 axios.create 初始化实例,通过环境变量区分开发/生产环境的 baseURL,还能设置超时时间、默认请求头等:
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASEURL, // 用环境变量动态切换域名
timeout: 10000, // 请求超时时间(毫秒)
headers: { 'Content-Type': 'application/json' }
})配置请求拦截器(处理通用请求逻辑)
请求发出去前,经常需要加 token、统一打开 loading、参数加密等,以“自动添加 token 到请求头”为例:
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token') // 从本地存储取 token
if (token) {
config.headers.Authorization = `Bearer ${token}` // 加到请求头
}
return config
},
(error) => {
return Promise.reject(error)
}
)配置响应拦截器(处理通用响应逻辑)
拿到后端响应后,要处理业务错误码、剥离无效数据、关闭 loading、token 过期跳转等,统一处理业务错误码”:
request.interceptors.response.use(
(response) => {
const res = response.data
if (res.code !== 200) { // 假设后端约定 200 为成功码
// 业务错误:弹提示、跳登录等
return Promise.reject(new Error(res.msg || '请求失败'))
}
return res // 只返回有效数据,简化业务层代码
},
(error) => {
// 网络错误、超时等处理
if (error.code === 'ECONNABORTED') { // 超时错误
alert('请求超时啦,检查下网络~')
}
return Promise.reject(error)
}
)接口模块化(按业务拆分接口)
把用户、商品、订单等接口按模块拆分到不同文件(如 user.js、goods.js),方便维护:
// user.js(用户模块接口)
import request from './request'
export function login(data) {
return request.post('/user/login', data)
}
export function getUserInfo() {
return request.get('/user/info')
}
// goods.js(商品模块接口)
export function getGoodsList(params) {
return request.get('/goods/list', { params })
}业务层调用(简洁复用)
在组件或逻辑层,直接导入接口函数调用,无需关心请求细节:
import { login } from '@/api/user'
async function handleLogin() {
try {
const res = await login({ username: 'xxx', password: 'xxx' })
console.log(res) // 直接拿到处理后的数据
} catch (error) {
console.log('登录失败', error)
}
}请求拦截器和响应拦截器,在 api service 里有啥实际作用?
拦截器是 axios api service 的“灵魂”,能帮我们把通用逻辑收拢到一处,避免在每个接口里重复写代码。
请求拦截器:发请求前的“预处理”
鉴权自动化:像前面例子中,自动给请求头加 token,不用每个接口手动写;
全局 loading 管理:请求拦截时打开 loading,响应拦截时关闭(需处理多请求并发场景,避免提前关闭);
参数加密:对敏感参数(如密码)加密后再发送,降低传输风险;
动态切换域名:根据接口路径,临时切换 baseURL(比如部分接口需调用第三方服务)。
举个“全局 loading 管理”的例子(用请求计数避免多请求冲突):
let requestCount = 0 // 记录同时进行的请求数
request.interceptors.request.use((config) => {
requestCount++
showLoading() // 显示全局 loading
return config
})
request.interceptors.response.use((response) => {
requestCount--
if (requestCount === 0) {
hideLoading() // 所有请求完成后关闭
}
return response
}, (error) => {
requestCount--
if (requestCount === 0) {
hideLoading()
}
return Promise.reject(error)
})响应拦截器:拿到响应后的“后处理”
剥离无效数据:后端返回一般是
{ code, msg, data },拦截器直接返回data,业务层无需重复写res.data;业务错误兜底:根据错误码(如 401 代表 token 过期),自动跳登录、弹提示;
数据解密:若后端返回加密数据,拦截器解密后再给业务层;
性能统计:记录请求开始和结束时间,计算接口耗时并上报。
接口请求失败了咋处理?还能做自动重试吗?
请求失败分业务错误(如参数错误、权限不足)和网络错误(如断网、超时、后端服务挂了),处理方式不同;自动重试适合“网络波动导致的临时失败”场景。
错误类型区分与基础处理
业务错误:由后端返回的错误码(如
code=500)触发,一般不重试,需提示用户(如“账号不存在”);网络错误:axios 走到
response拦截器的error分支,可通过error.message(如'Network Error')或error.code(如'ECONNABORTED'代表超时)判断。
自动重试方案
可借助 axios-retry 库,或自己写重试逻辑。
用 axios-retry 快速实现
安装依赖:npm i axios-retry,然后增强 axios 适配器:
import axios from 'axios'
import retryAdapterEnhancer from 'axios-retry'
const request = axios.create({
baseURL: '/api',
// 其他配置...
})
// 增强适配器,配置重试规则
const enhancedAdapter = retryAdapterEnhancer(axios.defaults.adapter, {
retries: 3, // 重试次数
retryCondition: (error) => {
// 仅对“网络错误”或“超时错误”重试
return (
axios.isNetworkError(error) ||
(error.code === 'ECONNABORTED' && error.message.includes('timeout'))
)
}
})
request.defaults.adapter = enhancedAdapter自己写重试逻辑(更灵活)
在响应拦截器中判断错误类型,并重发请求(需处理“重试次数”和“退避策略”避免无限重试):
request.interceptors.response.use(null, (error) => {
const { config } = error
if (!config || !config.retry) return Promise.reject(error) // 接口未配置重试则直接拒绝
config.retryCount = config.retryCount || 0 // 记录已重试次数
if (config.retryCount >= config.retry) { // 超过最大重试次数
return Promise.reject(error)
}
config.retryCount++
// 退避策略:每次重试间隔递增(如 1s → 2s → 4s...)
const delay = Math.pow(2, config.retryCount) * 1000
return new Promise((resolve) => {
setTimeout(() => {
resolve(request(config)) // 重新发起请求
}, delay)
})
})
// 业务层调用时,给接口配置 retry 参数
export function getGoodsList(params) {
return request.get('/goods/list', {
params,
retry: 3 // 该接口允许重试 3 次
})
}多个接口并发请求咋处理?axios 里有啥技巧?
日常开发中,“页面初始化时同时请求用户信息、菜单、购物车”这类并发请求很常见,axios 结合原生 Promise 能高效处理。
全成功才继续:Promise.all
适合“所有接口都成功,页面才渲染”的场景,只要有一个接口失败,整体进入 catch:
import { getUserInfo, getMenuList, getCart } from '@/api'
async function initPage() {
try {
const [userRes, menuRes, cartRes] = await Promise.all([
getUserInfo(),
getMenuList(),
getCart()
])
// 三个接口都成功,再处理数据
console.log(userRes, menuRes, cartRes)
} catch (error) {
console.log('初始化失败', error)
}
}不管成功失败都要结果:Promise.allSettled
适合“部分接口失败不影响页面核心功能”的场景,会返回所有请求的“成功/失败”状态:
async function initPage() {
const results = await Promise.allSettled([
getUserInfo(),
getMenuList(),
getCart()
])
results.forEach((result) => {
if (result.status === 'fulfilled') {
console.log('成功', result.value)
} else {
console.log('失败', result.reason)
}
})
}按顺序依赖请求:async/await 链式调用
适合“前一个接口的结果是后一个接口的参数”的场景(如先拿用户 ID,再查订单):
async function getOrderInfo() {
const user = await getUserInfo() // 先请求用户信息
const order = await getOrderByUserId(user.id) // 用用户 ID 请求订单
return order
}怎么给 axios api service 加缓存?避免重复请求相同接口?
缓存能减少重复请求、提升页面性能,分“拦截重复请求”和“数据缓存”两种场景。
拦截重复请求(避免短时间内重复发相同请求)
在请求拦截器中,记录“正在请求的接口(url + 参数)”,若重复则取消之前的请求:
const pendingRequests = new Map() // 存储待处理的请求
// 生成唯一 key(method + url + 参数)
function generateKey(config) {
return [config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data)].join('&')
}
request.interceptors.request.use((config) => {
const key = generateKey(config)
// 若有相同请求正在pending,取消它
if (pendingRequests.has(key)) {
const cancelToken = pendingRequests.get(key)
cancelToken.cancel('重复请求,已取消')
pendingRequests.delete(key)
}
// 为当前请求创建 CancelToken
config.cancelToken = new axios.CancelToken((cancel) => {
pendingRequests.set(key, { cancel })
})
return config
}, (error) => {
return Promise.reject(error)
})
request.interceptors.response.use((response) => {
const key = generateKey(response.config)
pendingRequests.delete(key) // 请求完成,从 pending 中移除
return response
}, (error) => {
if (axios.isCancel(error)) {
console.log('请求被取消', error.message)
} else {
const key = generateKey(error.config)
pendingRequests.delete(key)
}
return Promise.reject(error)
})数据缓存(重复请求直接读缓存)
适合“数据不常变化”的接口(如商品分类、静态配置),把响应数据存在内存或 localStorage:
const cache = new Map() // 内存缓存
const CACHE_EXPIRE = 60 * 1000 // 缓存有效期(1分钟)
request.interceptors.request.use((config) => {
if (config.method !== 'get') return config // 只缓存 get 请求
const key = generateKey(config)
const cached = cache.get(key)
// 若缓存存在且未过期,直接返回缓存数据
if (cached && Date.now() - cached.timestamp < CACHE_EXPIRE) {
return Promise.resolve(cached.data)
}
return config
}, (error) => {
return Promise.reject(error)
})
request.interceptors.response.use((response) => {
if (response.config.method === 'get') {
const key = generateKey(response.config)
cache.set(key, {
data: response,
timestamp: Date.now()
})
}
return response
}, (error) => {
return Promise.reject(error)
})前后端分离项目里,axios api service 咋和后端配合?
前后端分离后,“接口文档”是协作核心,axios api service 能让双方并行开发、高效联调。
先定文档,再并行开发
后端先输出接口文档(如 Swagger、Yapi),定义好 url、请求方法、参数格式、响应结构,前端根据文档,在 api service 里写好接口定义,并用 Mock 数据做前端页面。
以 Yapi 里的“登录接口”为例:
URL:
/user/loginMethod:
POST请求参数:
{ username: string, password: string }响应:
{ code: number, msg: string, data: { token: string } }
前端可先用 Mock.js 模拟响应:
import Mock from 'mockjs'
// 拦截登录接口,返回模拟数据
Mock.mock('/user/login', 'post', (req) => {
const { username, password } = JSON.parse(req.body)
if (username === 'mock' && password === 'mock') {
return { code: 200, msg: '登录成功', data: { token: Mock.Random.guid() } }
} else {
return { code: 400, msg: '账号密码错误' }
}
})这样前端能独立开发页面,后端同时开发真实接口,联调时只需切换 baseURL 到后端服务地址,无需修改接口调用代码。
联调时的问题排查
联调阶段,在拦截器中打印请求详情(url、参数、耗时),快速定位问题:
request.interceptors.request.use((config) => {
console.log('请求发送:', config.url, config.params || config.data)
config.requestStartTime = Date.now() // 记录请求开始时间
return config
})
request.interceptors.response.use((response) => {
const耗时 = Date.now() - response.config.requestStartTime
console.log('响应接收:', response.config.url, response.data, '耗时:', 耗时, 'ms')
return response
}, (error) => {
console.log('请求错误:', error.config.url, error.message)
return Promise.reject(error)
})后端返回不符合预期时,通过控制台的请求参数和响应数据,能快速判断是“前端传参错误”还是“后端逻辑错误”。
和 TypeScript 结合,axios api service 能玩出啥花样?
TypeScript 能给 axios api service 加上强类型约束,减少传参错误、响应解析错误等问题,提升代码健壮性。
给请求参数和响应数据加类型
定义接口的请求参数和响应的 TypeScript 类型,让编辑器自动提示、报错:
// types/user.d.ts(类型定义文件)
export interface LoginParams {
username: string
password: string
}
export interface 







网友评论文明上网理性发言 已有0人参与
发表评论: