Stories

Detail Return Return

深入理解 Axios 攔截器與 Promise 異步機制:從源碼角度剖析異步編程的本質 - Stories Detail

深入理解 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 的關係

如果這篇文章對你有幫助,請點贊支持!有問題歡迎在評論區討論。 🚀

user avatar pengxiaohei Avatar evenboy Avatar zhuifengdekukafei Avatar code500g Avatar fanudekaixinguo Avatar zohocrm Avatar pulsgarney Avatar maililuo Avatar yangy5hqv Avatar minghuajiwu Avatar kanjianliao Avatar jump_and_jump Avatar
Favorites 17 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.