【axios源码】- 取消请求cancel模块研读解析
首發于我的公眾號「前端面壁者」,歡迎關注。
基于 TC39 的 cancelable promises proposal 提議封裝,但是這個提議已經被發起人自己取消了,據說是因為 Google 內部反對意見很大,詳情可以到相關 issueWhy was this proposal withdrawn?看一下。
一、環境準備
-
axios 版本 v0.24.0
-
通過 github1s 網頁可以 查看 axios 源碼
-
調試需要 clone 到本地
二、函數研讀
cancel 模塊包含三個文件Cancel、CancelToken、isCancel
├─cancel // 取消請求 │ Cancel.js │ CancelToken.js │ isCancel.js1. Cancel 類
A Cancel is an object that is thrown when an operation is canceled.
當操作被取消時會拋出一個 Cancel 對象
- 只有 8 行代碼,實現了一個Cancel 類
- 包含一個原型方法 toString ,一個實例屬性__CANCEL__
- 實例屬性__CANCEL__被設定為 true,是 Cancel 類的標志
Tips:我們知道一個請求會有三種狀態請求前、請求中、請求結束,取消動作可能發生在其中的任何一個階段。不同階段對應的取消邏輯在axios中是不一樣的,且在請求中這個階段,函數執行的邏輯位置也不一樣,所以當我們發起取消請求時,axios需要一種機制獲知當前網絡請求走到了哪個階段的哪個位置才能執行對應的取消和返回邏輯,Cancel 類就是這樣一個機制。
2. isCancel 函數
"use strict";module.exports = function isCancel(value) {return !!(value && value.__CANCEL__); };- 只有 3 行代碼,通過 Cancel 的實例屬性__CANCEL__ 來判斷value是否為 Cancel 實例
- 導出一個 isCancel 函數,如果入參 val 不是 Cancel類會返回 false,否則返回 true
3. CancelToken 類
A CancelToken is an object that can be used to request cancellation of an operation.
CancelToken 是被用于取消請求操作的類
參考文章林景宜的記事本提到這是一個讓人一頭霧水的類,剛看的時候筆者也有同感,雖然經過一些剖析筆者已經分析的盡可能詳細(自我感覺良好 🐶),但在觀看之前還是推薦不了解設計模式的小伙伴去了解一下其中的發布/訂閱模式,會有助于理解
【2.1】 引入 Cancel 類
"use strict";var Cancel = require("./Cancel");【2.2】 內部函數 CancelToken
兩段this.promise.then并不是取消邏輯,暫時先忽略
/*** A `CancelToken` is an object that can be used to request cancellation of an operation.** @class* @param {Function} executor The executor function.*/ function CancelToken(executor) {if (typeof executor !== "function") {throw new TypeError("executor must be a function.");}var resolvePromise;this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});var token = this;// eslint-disable-next-line func-namesthis.promise.then(function (cancel) {if (!token._listeners) return;var i;var l = token._listeners.length;for (i = 0; i < l; i++) {token._listeners[i](cancel);}token._listeners = null;});// eslint-disable-next-line func-namesthis.promise.then = function (onfulfilled) {var _resolve;// eslint-disable-next-line func-namesvar promise = new Promise(function (resolve) {token.subscribe(resolve);_resolve = resolve;}).then(onfulfilled);promise.cancel = function reject() {token.unsubscribe(_resolve);};return promise;};executor(function cancel(message) {if (token.reason) {// Cancellation has already been requestedreturn;}token.reason = new Cancel(message);resolvePromise(token.reason);}); }接下來按行分析
-
1. 首先判斷入參executor是否是 function 類型,如果不是會拋出類型錯 TypeError
- 為什么入參executor是一個函數?
- 答:CancelToken通過發布訂閱模式來實現傳遞取消信息,訂閱者把自己想訂閱的事件cancel/c注冊到調度中心CancelToken,當適配器返回信息時發布者發布到調度中心CancelToken,調度中心再統一調度執行訂閱者注冊到調度中心的處理代碼cancel。這也就理解了這個函數為什么叫executor-執行器。
-
2. 創建全局變量resolvePromise
- 為什么要創建這個變量?為什么要以全局的方式創建?
- 答:為了拿到resolve便于后續的鏈式調用,且只有全局變量才能穿過函數作用域拿到 resolve 值
-
3. 使用new Promise()創建函數promiseExecutor的實例對象,表明executor是一個異步函數,其返回值resolve傳遞給全局變量resolvePromise,調用時可使用this.promise.then()獲取返回值resolve或直接使用resolvePromise
- 為什么創建 promise 對象?
- 因為真正取消請求的動作request.abort()在適配器xhr.js或http.js里觸發,這是一個異步的方法,當這個動作發生后我們才能在 resolve 中拿到返回值
-
4. 創建全局變量token表示當前CancelToken的實例
-
5. executor執行器執行訂閱者事件cancel ,該方法入參message來自用戶調用時傳參
-
6. 訂閱者事件cancel在執行時首先會判斷實例token是否存在reason屬性值,如果存在直接返回,否則使用new Cancel()聲明一個 cancel 類給reason賦值
- 為什么用new Cancel()聲明一個 cancel 類給reason賦值?
- 答:這樣reason的原型鏈上會掛載一個__CANCEL__表示 cancel 類,同時配合resolvePromise執行會把this.promise實例狀態改為fulfilled,這意味著是用戶主動取消請求返回的信息而非因為其他異常返回(會把this.promise實例狀態改為reject)
CancelToken 構造函數就是一個發布訂閱函數,通過發布訂閱觸發向外 resolve 或者拋出錯誤(reject),Promise 鏈式結構成功拿到resolve值或者 catch 到錯誤后,會返回用戶的輸入message或停止繼續執行并執行錯誤回調。
【2.3】 添加原型方法 throwIfRequested
/*** Throws a `Cancel` if cancellation has been requested.*/ CancelToken.prototype.throwIfRequested = function throwIfRequested() {if (this.reason) {throw this.reason;} };- 如果已觸發取消事件,則拋出一個錯誤信息
【2.4】 source
/*** Returns an object that contains a new `CancelToken` and a function that, when called,* cancels the `CancelToken`.*/ CancelToken.source = function source() {var cancel;var token = new CancelToken(function executor(c) {cancel = c;});return {token: token,cancel: cancel,}; };- 初始化時返回值是一個對象,包含新的CancelToken對象 token 和函數 cancel
- 執行時訂閱者會將訂閱事件c注冊到調度中心CancelToken,調度中心在執行器executor中立即執行訂閱者事件
- cancel函數通過全局作用域拿到c,通過 return 暴露給用戶一個訂閱事件的入口
【2.5】 添加原型方法 subscribe
/*** Subscribe to the cancel signal*/CancelToken.prototype.subscribe = function subscribe(listener) {if (this.reason) {listener(this.reason);return;}if (this._listeners) {this._listeners.push(listener);} else {this._listeners = [listener];} };- 添加訂閱時執行的函數,訂閱事件cancel被執行時,會向當前實例的_listeners屬性上追加鏈式調用的返回值
【2.6】 添加原型方法 unsubscribe
/*** Unsubscribe from the cancel signal*/CancelToken.prototype.unsubscribe = function unsubscribe(listener) {if (!this._listeners) {return;}var index = this._listeners.indexOf(listener);if (index !== -1) {this._listeners.splice(index, 1);} };- 添加訂閱時的執行的函數,訂閱事件cancel被執行時,如果是重復取消就移除當前實例的_listeners屬性上對應位置的鏈式調用
三、參考
1. 仙凌閣的文章詳細 Axios 源碼解讀
2. 林景宜的文章林景宜的記事本 - Axios 源碼解析(二):通用工具方法
3. 若川的文章學習 axios 源碼整體架構,打造屬于自己的請求庫
4. 杰凌的文章深入解讀 axios 源碼
5. 海角在眼前的文章設計模式(三):觀察者模式與發布/訂閱模式區別
總結
以上是生活随笔為你收集整理的【axios源码】- 取消请求cancel模块研读解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 360收藏夹html文件,360浏览器收
- 下一篇: [ Android实战 ] java.l