深入理解 Axios 拦截器与 Promise 异步机制:从源码角度剖析异步编程的本质

211 阅读8分钟

深入理解 Axios 拦截器与 Promise 异步机制:从源码角度剖析异步编程的本质

本文将带你从 Promise 基础概念出发,深入理解 Axios 拦截器的内部实现原理,揭秘异步编程背后的核心机制。

📖 前言

在前端开发中,我们经常使用 Axios 进行 HTTP 请求,并通过拦截器来统一处理请求和响应。但你是否真正理解拦截器背后的工作原理?为什么拦截器能够按顺序执行?为什么有时候拦截器的 onRejected 不会被触发?

本文将从 Promise 的基础概念开始,逐步深入到 Axios 拦截器的内部实现,帮你彻底理解这套异步机制的本质。

1️⃣ Promise 基础概念深入解析

1.1 Promise 三种状态详解

Promise 作为 JavaScript 中处理异步操作的核心机制,具有三种状态:

状态描述转换条件
pending初始状态,既未成功也未失败
fulfilled成功态调用 resolve(value)
rejected失败态调用 reject(reason)

核心特性

  • 状态一旦确定(fulfilled/rejected),就不可逆转
  • 状态转换只能是:pending → fulfilled 或 pending → rejected

1.2 Promise 构造与执行机制

let p = new Promise((resolve, reject) => {
    console.log('这里立即同步执行');  // ✅ 构造函数立即执行
    setTimeout(() => resolve('value'), 1000);  // 异步操作
});

关键理解

  • resolve(value) → Promise 状态变为 fulfilled,值为 value
  • reject(reason) → Promise 状态变为 rejected,拒绝原因为 reason
  • 构造函数内的代码立即同步执行
  • 只有 resolve/reject 的调用会安排微任务到微任务队列

1.3 then 方法的核心机制

p.then(onFulfilled, onRejected)

then 方法的本质

  • 返回一个新的 Promise(我们称它为 p2

  • 参数说明

    • onFulfilled(value) → 处理成功态,接收原 Promise resolve 的值
    • onRejected(reason) → 处理失败态,接收原 Promise reject 的值

状态决定规则

  1. 回调返回普通值 → 新 Promise 成功 (p2),值为返回值(undefined 也算普通值)
  2. 回调返回 Promise → 新 Promise (p2) 状态跟随返回的 Promise (p3)
  3. 回调抛出异常 → 新 Promise 失败 (p2),拒绝原因是异常

重要细节

  • 如果只传 onFulfilled,原 Promise rejected → 新 Promise 继承原状态
  • 同一个 Promise 的状态一旦确定,不会改变

1.4 catch 方法本质

p.catch(onRejected)

// 等价于:
p.then(undefined, onRejected)
  • 捕获链上前一个 Promise 的 reject
  • 回调返回值的状态规则与 then 相同

1.5 Promise 链中的状态传递总结

链中节点状态来源
第一个 Promise构造函数中调用 resolve/reject
第二个 Promise (then 返回)回调返回值或异常,若回调未传且状态不匹配,则继承前一个状态
第三个 Promise (catch 返回)回调返回值或异常,若返回 Promise,则新 Promise 状态跟随返回的 Promise

2️⃣ Axios 拦截器机制深度剖析

2.1 请求拦截器详解

axios.interceptors.request.use(
  function onFulfilled(config) { 
    // 处理请求配置
    return config;
  },
  function onRejected(error) { 
    // 处理拦截器链中的错误
    return Promise.reject(error);
  }
)
onFulfilled 参数详解
  • 参数 config:请求配置对象(JavaScript 对象)
  • 类型Object
  • 内容示例
{
  url: 'http://example.com',
  method: 'get',
  headers: { ... },
  params: {},
  data: null,
  timeout: 5000
}

可以修改的内容

  • headers(添加 token、修改 Content-Type)
  • params(查询参数)
  • data(请求体数据)
  • timeout(超时时间)

返回值处理

  • 普通对象 → 传递给下一环节(或发送请求)
  • Promise → Axios 会等待 Promise resolve 再继续
onRejected 触发条件
  • 只有当前一个拦截器的 onFulfilled 抛错或返回 Promise.reject(...) 才会触发
  • 处理拦截器链的异常情况
执行时机重点
  • 请求拦截器的 onFulfilled 在请求发送前执行
  • 并不是网络请求成功后触发
  • 即使 Promise 状态是 fulfilled,onFulfilled 也只处理 config,不依赖网络结果
关键特性:单拦截器错误处理
axios.interceptors.request.use(
  config => {
    throw new Error('出错了');  // 这里抛错
  },
  error => {
    console.log('这里不会执行');  // ❌ 不会触发本拦截器的 onRejected
    return Promise.reject(error);
  }
);

原因:拦截器内部实现相当于 Promise.resolve(config).then(onFulfilled, onRejected),onFulfilled 抛错产生的新 rejected Promise 不会回到当前 then 的 onRejected。

2.2 响应拦截器详解

axios.interceptors.response.use(
  function onFulfilled(response) { 
    // 处理成功响应
    return response;
  },
  function onRejected(error) { 
    // 处理失败响应
    return Promise.reject(error);
  }
)
onFulfilled 触发条件
  • 网络请求成功后触发
  • response.data 是实际返回内容
  • 状态码 2xx 范围内的响应
onRejected 触发条件
// 以下情况会触发响应拦截器的 onRejected:
// 1. HTTP 状态码 >= 400 (如 404, 500)
// 2. 网络错误(超时、无网络等)
// 3. 上一个拦截器抛出异常
// 4. 请求被取消

2.3 拦截器执行顺序的关键差异

⚠️ 重要:请求拦截器和响应拦截器的执行顺序不同!

// 注册拦截器
axios.interceptors.request.use(config => {
    console.log('请求拦截器 1');
    return config;
});

axios.interceptors.request.use(config => {
    console.log('请求拦截器 2');
    return config;
});

axios.interceptors.response.use(response => {
    console.log('响应拦截器 1');
    return response;
});

axios.interceptors.response.use(response => {
    console.log('响应拦截器 2');
    return response;
});

// 执行顺序:
// 请求拦截器 2 → 请求拦截器 1 → 发送请求 → 响应拦截器 1 → 响应拦截器 2
  • 请求拦截器倒序执行(后注册的先执行)
  • 响应拦截器正序执行(先注册的先执行)

3️⃣ Axios 内部实现原理深度解析

3.1 核心执行流程

让我们深入 Axios 源码,理解拦截器的实现原理:

// Axios 内部 request 方法(简化版)
function request(config) {
  // 1. 合并配置
  config = mergeConfig(this.defaults, config);
  
  // 2. 🎯 创建初始 Promise - 关键步骤!
  let promise = Promise.resolve(config);
  
  // 3. 应用请求拦截器(倒序)
  this.interceptors.request.forEachReverse((interceptor) => {
    promise = promise.then(
      interceptor.fulfilled,    // 用户传入的第一个函数
      interceptor.rejected      // 用户传入的第二个函数
    );
  });
  
  // 4. 发送实际网络请求
  promise = promise.then(dispatchRequest, undefined);
  
  // 5. 应用响应拦截器(正序)
  this.interceptors.response.forEach((interceptor) => {
    promise = promise.then(
      interceptor.fulfilled,
      interceptor.rejected
    );
  });
  
  return promise;  // 返回最终 Promise 给用户
}

3.2 Promise.resolve(config) 的关键作用

Q:这个 Promise 对象是谁创建的? A:是 Axios 内部代码创建的,不是用户创建的。

创建时机和作用
// 用户调用
axios.get('/api/users');

// Axios 内部立即执行:
let config = { url: '/api/users', method: 'get', /* 默认配置... */ };
let promise = Promise.resolve(config);  // 🎯 在这里创建!

// 此时 promise 的状态:
// - state: fulfilled  
// - value: config 对象
为什么要用 Promise.resolve(config)?
// Promise.resolve() 的作用:创建一个立即 fulfilled 的 Promise
Promise.resolve('hello')  // 立即创建 fulfilled 状态的 Promise

// 等价于:
new Promise((resolve) => {
  resolve('hello');
});

// 在 Axios 中的作用:
let promise = Promise.resolve(config);  // 立即 fulfilled,值是 config

// 这样第一个拦截器就能立即接收到 config:
promise.then(function(config) {  
  console.log('收到配置:', config);  // config 就是上面的配置对象
  return config;
});

3.3 完整执行示例

// 1. 用户调用
axios.get('/api/users');

// 2. Axios 内部执行流程:
function request(config) {
  // Step 1: 准备配置
  config = { url: '/api/users', method: 'get', headers: {}, /*...*/ };
  
  // Step 2: 创建起始 Promise(fulfilled 状态,值为 config)
  let promise = Promise.resolve(config);
  
  // Step 3: 应用请求拦截器链
  // promise = promise.then(interceptor2.fulfilled, interceptor2.rejected);
  // promise = promise.then(interceptor1.fulfilled, interceptor1.rejected);
  
  // Step 4: 发送网络请求
  // promise = promise.then(dispatchRequest);  // 返回包含 response 的 Promise
  
  // Step 5: 应用响应拦截器链
  // promise = promise.then(responseInterceptor1.fulfilled, responseInterceptor1.rejected);
  // promise = promise.then(responseInterceptor2.fulfilled, responseInterceptor2.rejected);
  
  // Step 6: 返回最终 Promise 给用户
  return promise;
}

3.4 拦截器存储结构

// Axios 内部存储拦截器的结构(简化)
class InterceptorManager {
  constructor() {
    this.handlers = [];  // 存储拦截器数组
  }
  
  use(fulfilled, rejected) {
    this.handlers.push({
      fulfilled,  // 用户传入的第一个函数
      rejected    // 用户传入的第二个函数
    });
    return this.handlers.length - 1;  // 返回索引,用于移除拦截器
  }
  
  forEach(fn) {
    this.handlers.forEach(handler => {
      if (handler !== null) {
        fn(handler);
      }
    });
  }
}

// 使用示例:
const requestInterceptors = new InterceptorManager();
requestInterceptors.use(
  function(config) { return config; },    // 存储为 handler.fulfilled
  function(error) { return Promise.reject(error); }  // 存储为 handler.rejected
);

4️⃣ 核心知识点总结

4.1 Promise 核心机制

  1. Promise 是状态机

    • pending → fulfilled / rejected
    • 状态不可逆
    • then/catch 返回新的 Promise
  2. then 参数机制

    • onFulfilled:前一个 Promise fulfilled 时执行
    • onRejected:前一个 Promise rejected 时执行
    • 回调返回值决定新 Promise 状态
  3. catch 是 then 的语法糖

    • p.catch(fn) = p.then(undefined, fn)

4.2 Axios 拦截器核心机制

  1. 拦截器链本质是 Promise 链

    • 请求拦截器 onFulfilled:处理 config → 在请求发送前执行
    • 请求拦截器 onRejected:处理拦截器链中前一个 reject
    • 响应拦截器 onFulfilled:处理网络请求成功的 response
    • 响应拦截器 onRejected:处理网络请求失败或上一个拦截器异常
  2. 执行顺序差异

    • 请求拦截器:倒序执行(后注册先执行)
    • 响应拦截器:正序执行(先注册先执行)
  3. 拦截器返回值规则

    • 返回普通值 → 继续向下传递
    • 返回 Promise → 等待 Promise resolve/reject
    • 抛出异常 → 下一个 onRejected 捕获

4.3 单拦截器 vs 多拦截器差异

// 单拦截器:onFulfilled 抛错不会触发本拦截器的 onRejected
axios.interceptors.request.use(
  config => { throw new Error('错误'); },  // 抛错
  error => { console.log('不执行'); }      // 不会执行
);

// 多拦截器:前一个拦截器 reject → 下一个拦截器 onRejected 执行
axios.interceptors.request.use(
  config => { throw new Error('错误'); },  // 第一个拦截器抛错
  error => { console.log('不执行'); }      // 不会执行
);
axios.interceptors.request.use(
  config => { return config; },
  error => { console.log('会执行'); }      // 会捕获上一个拦截器的错误
);

5️⃣ 实战应用场景

5.1 统一添加认证 Token

axios.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

5.2 统一错误处理

axios.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.status === 401) {
      // 跳转到登录页
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

5.3 请求和响应日志记录

// 请求日志
axios.interceptors.request.use(config => {
  console.log(`发送请求: ${config.method?.toUpperCase()} ${config.url}`);
  return config;
});

// 响应日志
axios.interceptors.response.use(
  response => {
    console.log(`响应成功: ${response.status} ${response.config.url}`);
    return response;
  },
  error => {
    console.error(`请求失败: ${error.message}`);
    return Promise.reject(error);
  }
);

6️⃣ 总结

通过本文的深入分析,我们了解到:

  1. Axios 拦截器的本质是 Promise 链,每个拦截器都是链上的一个节点
  2. Promise.resolve(config) 是整个链的起点,由 Axios 内部创建
  3. 拦截器的执行顺序有差异:请求拦截器倒序,响应拦截器正序
  4. 单拦截器内部的错误处理机制:onFulfilled 抛错不会触发本拦截器的 onRejected
  5. 理解 Promise 状态传递机制是掌握拦截器的关键

这些概念不仅适用于 Axios,对于理解其他基于 Promise 的异步库(如 fetch API 的封装库)也有重要意义。掌握了这些原理,你就能更好地设计和调试复杂的异步处理逻辑。


💡 延伸阅读建议

  • Promise/A+ 规范
  • Axios 源码分析
  • 微任务与宏任务机制
  • async/await 与 Promise 的关系

如果这篇文章对你有帮助,请点赞支持!有问题欢迎在评论区讨论。 🚀