×

请求死活发不出去,控制台也没报错?

提问者:Terry2025.12.18浏览:27

做前端开发时,只要涉及接口请求,十有八九会用到 axios,但用的时候总会碰到各种状况——请求发不出去、响应数据解析错、拦截器不好使、跨域报错…好多 axios user (也就是用 axios 的开发者)都愁这些事儿,这篇文章把大家高频遇到的问题拆成一个个小疑问,挨个讲清楚咋分析、咋解决,帮你把 axios 用顺溜~

碰到这种“无声失败”,得从几个关键环节排查:

首先检查请求配置method 写成大写“GET”没问题,但要是写成“Get”“METHOD”这类,axios 不认,请求就发不出去;还有 url 拼接错误,比如把 /api/user 写成 /api/user/ 带了多余斜杠,或者 baseURLurl 拼接后重复(baseURLhttps://xxx.com/apiurl 又写了 /api/user,最后变成 https://xxx.com/api/api/user 导致 404,不过这种情况一般会有响应报错,也得检查)。

然后看请求拦截器,axios 的请求拦截器 interceptors.request.use 里,必须把配置对象 config return 出去,要是忘了 return,请求就被“吞”了,举个错例:

axios.interceptors.request.use(config => {
  config.headers.Authorization = 'Bearer ' + token;
  // 这里没写 return config,请求就发不出去!
});

改成 return config 就好。

再排查网络和后端状态,打开浏览器开发者工具的 Network 面板,看请求到底发没发出去,要是没看到请求记录,可能是前端代码逻辑问题;要是看到请求但状态是 pending 或失败,得检查后端服务是否正常(比如用 Postman 单独调接口试试),或者本地代理配置错了(Vue 项目里 devServer.proxy 配歪了,把请求导到不存在的地址)。

最后抓包验证,用 Charles 或 Fiddler 抓包,确认请求是否真的从本机发出去了,有时候浏览器插件(比如广告拦截)会误拦请求,临时关了试试也有用。

响应数据拿到了,但格式不对咋整?

最常见的是响应数据解析不匹配,比如后端返回二进制文件(像 Excel、图片),但前端没配 responseType: 'blob',axios 会默认按 json 解析,结果拿到一堆乱码;反过来,后端返回 json 字符串,前端却设了 responseType: 'text',也会导致 response.data 是字符串没法直接用。

解决思路分两步:先和后端确认返回格式,再对应配置 responseType,举个文件下载的例子:
后端返回 blob 格式的 Excel 文件,前端请求得这么写:

axios.get('/download/excel', {
  responseType: 'blob' // 关键配置!
}).then(res => {
  // res.data 是 blob 对象,可用于创建下载链接
  const url = URL.createObjectURL(res.data);
  const a = document.createElement('a');
  a.href = url;
  a.download = '报表.xlsx';
  a.click();
});

要是碰到自定义数据格式(比如后端返回加密字符串,需要前端解密),可以用 transformResponse 处理,axios 允许在拿到响应后、进入 then 回调前,自定义解析逻辑:

const instance = axios.create({
  transformResponse: [data => {
    // 假设后端返回的是加密字符串,这里解密
    return decrypt(data); 
  }]
});
instance.get('/api/secret').then(res => {
  // res.data 已经是解密后的数据
});

拦截器里想加 token,但是登录态还没拿到咋处理?

很多项目里,token 是用户登录后存在全局状态(Vuex、Redux)里的,而请求拦截器可能在页面加载时就执行了,这时候 token 还没拿到,请求头加不上,这得处理异步获取 token的逻辑。

用 Promise 把 token 获取逻辑包起来,在请求拦截器里,先判断 token 是否存在,不存在就先发起“获取 token”的请求(但要注意避免循环请求,比如登录接口本身不需要 token),示例:

const instance = axios.create();
let tokenPromise = null; // 用来缓存获取 token 的 Promise
instance.interceptors.request.use(async config => {
  if (!token) { // 假设 token 是全局变量,初始为 null
    if (!tokenPromise) { // 避免重复发请求
      tokenPromise = getTokenFromServer(); // 假设这是请求后端拿 token 的接口
    }
    token = await tokenPromise;
    tokenPromise = null; // 用完清空,下次重新获取
  }
  config.headers.Authorization = 'Bearer ' + token;
  return config;
});

把登录逻辑提前,比如在路由守卫里,确保进入需要权限的页面时,token 已经存在,或者在 App 组件挂载时,先请求获取 token 再渲染页面,避免请求拦截器执行时 token 缺失。

要注意的是,这种异步处理容易引发竞态问题(比如多个请求同时触发,导致重复请求 token),所以用变量缓存 Promise 很关键,保证同一时间只有一个“获取 token”的请求在飞。

同时发多个请求,要等都完成再处理咋做?

这种场景特常见,比如页面初始化时,需要用户信息、菜单权限、站点配置三个接口都返回,才能渲染页面,这时候得用并发请求控制

axios 里可以用 Promise.all(因为 axios 的请求本身返回 Promise),示例:

const request1 = axios.get('/api/user');
const request2 = axios.get('/api/menu');
const request3 = axios.get('/api/config');
Promise.all([request1, request2, request3])
  .then(([res1, res2, res3]) => {
    // 三个请求都成功后,处理数据
    console.log(res1.data, res2.data, res3.data);
    renderPage();
  })
  .catch(error => {
    // 只要有一个请求失败,就进这里
    console.error('至少一个请求失败:', error);
  });

要是想允许部分失败(比如三个请求里,即使一个失败,另外两个的结果也要用),可以用 Promise.allSettled,它会返回每个请求的状态(fulfilledrejected)和结果:

Promise.allSettled([request1, request2, request3])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('成功:', result.value.data);
      } else {
        console.error('失败:', result.reason);
      }
    });
  });

实际项目里,还可以封装一个工具函数,专门处理多请求并发,比如加 loading 状态、错误提示统一处理,让代码更整洁。

请求发出去了,想中途取消咋实现?

比如用户在搜索框连续输入,每次输入都发请求,但希望上一次的请求自动取消,避免旧请求覆盖新请求结果(俗称“竞态问题”),这时候得用请求取消功能。

axios 现在推荐用AbortController(这是浏览器原生 API,兼容性好),取代旧的 cancelToken(已标记为 Deprecated),用法如下:

// 创建控制器
const controller = new AbortController();
// 请求时传入 signal
axios.get('/api/search', {
  signal: controller.signal
}).then(res => {
  console.log('请求成功:', res.data);
}).catch(error => {
  if (axios.isCancel(error)) {
    console.log('请求被取消了:', error.message);
  } else {
    console.error('其他错误:', error);
  }
});
// 想要取消时,调用 abort
controller.abort();

要是处理连续搜索的场景,可以每次输入时,先取消上一次的请求:

let abortController = null;
input.addEventListener('input', () => {
  // 取消上一次的请求
  if (abortController) {
    abortController.abort();
  }
  // 新建控制器
  abortController = new AbortController();
  axios.get('/api/search?q=' + input.value, {
    signal: abortController.signal
  }).then(/* 处理响应 */);
});

旧项目里如果还在用 cancelToken,了解下用法即可(但建议升级):

const source = axios.CancelToken.source();
axios.get('/api', {
  cancelToken: source.token
}).catch(error => {
  if (axios.isCancel(error)) { ... }
});
source.cancel('手动取消请求');

跨域报错“Access-Control-Allow-Origin”咋破?

跨域问题分开发环境生产环境两种情况处理。

开发时,前端可以用代理绕过浏览器的同源策略,Vue 项目在 vue.config.js 里配:

module.exports = {
  devServer: {
    proxy: {
      '/api': { // 把以 /api 开头的请求,代理到后端域名
        target: 'https://xxx.com',
        changeOrigin: true, // 开启跨域
        pathRewrite: { '^/api': '' } // 去掉 /api 前缀
      }
    }
  }
};

React 项目(create-react-app)可以在 src/setupProxy.js 里用 http-proxy-middleware 配代理,原理一样,这样前端请求 http://localhost:8080/api/user 就会被代理到 https://xxx.com/user,浏览器以为是同域请求,不会触发 CORS 检查。

生产环境中,得让后端配置 CORS 响应头,后端需要设置:

  • Access-Control-Allow-Origin: *(允许所有域名,生产建议指定具体域名,https://your-frontend.com 更安全)

  • Access-Control-Allow-Headers: Content-Type, Authorization(允许前端带的请求头,token 就在 Authorization 里)

  • Access-Control-Allow-Methods: GET, POST, PUT, DELETE(允许的请求方法)

对于带自定义请求头(Authorization)或非简单请求(PUT/DELETE 等),浏览器会先发OPTIONS 预检请求,后端必须处理 OPTIONS 请求,返回上述 CORS 头,否则跨域还是会失败。

举个 Java 后端的例子(Spring Boot),配置 CORS:

@Configuration
public class CorsConfig implements WebMvcConfigurer {
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**") // 对所有接口生效
      .allowedOrigins("https://your-frontend.com") // 允许的前端域名
      .allowedMethods("*") // 允许所有方法
      .allowedHeaders("*") // 允许所有请求头
      .allowCredentials(true); // 允许携带 cookie(如果需要)
  }
}

在 Vue/React 项目里,怎么封装 axios 更顺手?

封装的核心是创建自定义 axios 实例,统一处理请求拦截、响应拦截、错误处理,减少重复代码。

以 Vue 项目为例,步骤如下:

  1. 创建 axios 实例,配置 baseURL、超时时间:

    import axios from 'axios';
    const instance = axios.create({
    baseURL: process.env.VUE_APP_API_BASE, // 从环境变量拿基础地址
    timeout: 10000 // 请求超时时间
    });
  2. 请求拦截器加 token

    instance.interceptors.request.use(config => {
    const token = localStorage.getItem('token'); // 假设 token 存在 localStorage
    if (token) {
     config.headers.Authorization = 'Bearer ' + token;
    }
    return config;
    }, error => {
    return Promise.reject(error);
    });
  3. 响应拦截器处理业务错误(比如后端返回 code≠0 表示失败):

    instance.interceptors.response.use(response => {
    const res = response.data;
    if (res.code === 0) { // 假设后端约定 code=0 为成功
     return res.data; // 只把业务数据返回给业务代码
    } else {
     // 业务错误,code=1001 表示登录过期
     if (res.code === 1001) {
       // 跳转到登录页
       router.push('/login');
     }
     return Promise.reject(new Error(res.msg || '请求失败'));
    }
    }, error => {
    // 处理 HTTP 状态码错误,404、500
    if (error.response) {
     console.error('HTTP 错误状态码:', error.response.status);
    } else if (error.request) {
     // 请求发出去了,但没收到响应(比如超时)
     console.error('请求超时或无响应');
    }
    return Promise.reject(error);
    });
  4. 挂载到 Vue 原型,方便全局使用:

    import Vue from 'vue';
    Vue.prototype.$http = instance;
    // 组件里用 this.$http.get('/user') 发起请求

React 项目可以用自定义 Hook封装,或者把实例导出,在需要的地方引入,核心逻辑和 Vue 类似,都是通过拦截器统一处理登录态、错误码。

重复请求咋拦截?比如用户连续点按钮发了多个相同请求?

用户快速点提交按钮,可能导致同一个接口发多次请求,后端处理重复操作(比如重复下单)就会出问题,这时候要做重复请求拦截

思路是:在请求拦截器中,记录“正在请求”的请求标识(url + method + 参数),如果相同标识的请求已经在发,就取消之前的请求,只保留最后一个。

具体实现:用 Map 存请求标识和对应的取消函数(AbortController)。

const pendingRequests = new Map(); // key: 请求标识,value: AbortController
// 生成请求标识的函数
function generateKey(config) {
  return [config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data)].join('&');
}
const instance = axios.create();
// 请求拦截器:记录 pending 请求
instance.interceptors.request.use(config => {
  const key = generateKey(config);
  // 如果有重复请求,取消之前的
  if (pendingRequests.has(key)) {
    const controller = pendingRequests.get(key);
    controller.abort(); // 取消请求
    pendingRequests.delete(key); // 从 Map 中移除
  }
  // 创建新的 AbortController
  const controller = new AbortController();
  config.signal = controller.signal;
  pendingRequests.set(key, controller);
  return config;
}, error => {
  return Promise.reject(error);
});
// 响应拦截器:请求完成后移除记录
instance.interceptors.response.use(response => {
  const key = generateKey(response.config);
  pendingRequests.delete(key);
  return response;
}, error => {
  if (axios.isCancel(error)) {
    // 取消的请求,不需要处理
  } else {
    const key = generateKey(error.config);
    pendingRequests.delete(key);
  }
  return Promise.reject(error);
});

这样,当用户连续点击按钮发相同请求时,只有最后一次请求会真正发出去,之前的都会被取消,避免重复操作。

后端返回 500、404 这些状态码,咋统一处理?

axios 把 HTTP 状态码非 2xx 的响应都视为错误,会进入 catch 回调,我们可以在响应拦截器错误处理里统一处理。

首先看响应拦截器(针对有响应的情况,404、500):

instance.interceptors.response.use(
  response => {
    // 2xx 状态码走这里,正常返回
    return response;
  },
  error => {
    if (error.response) {
      // 有响应,说明服务器返回了状态码(非2xx)
      const status = error.response.status;
      switch (status) {
        case 401:
          // 未授权,跳登录页
          router.push('/login');
          break;
        case 403:
          // 权限不足,提示并跳转到无权限页面
          ElMessage.error('你没有权限访问');
          router.push('/403');
          break;
        case 404:
          // 资源不存在,提示并跳404页面
          ElMessage.error('页面走丢了~');
          router.push('/404');
          break;
        case 500:
          // 服务器错误,提示重试
          ElMessage.error('服务器开小差了,稍后再试~');
          break;
        default:
          ElMessage.error('请求出错啦');
      }
    } else if (error.request) {
      // 没有响应,请求发出去了但没

您的支持是我们创作的动力!

网友回答文明上网理性发言 已有0人参与

发表评论: