×

axios api service 是什么?和直接用 axios 发请求有啥区别?

作者:Terry2025.12.11来源:Web前端之家浏览:37评论:0
关键词:axiosapi service

axios api service 是什么?和直接用 axios 发请求有啥区别?

做前端开发时,和后端接口打交道是家常便饭,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.jsgoods.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/login

  • Method: 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

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://www.jiangweishan.com/article/sdjfjjsdhh235623423.html

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

发表评论: