Axios源碼分析
Axios是一個基於promise的HTTP庫,可以用在瀏覽器和node.js中。
1.請求配置
1 { 2 // 請求伺服器的URL 3 url: '/user', 4 5 // method 創建請求使用的方法 6 method: 'get' 7 8 // baseURL 將自動加早url前面,除非 url 是一個絕對url 9 baseURL: '//some-domain.com/api/' 10 11 // 'transformRequest' 允許向伺服器發送前,修改請求數據 12 // 只能用在 PUT, POST 和 PATH 這幾個請求方法 13 // 後面數組中的函數必須返回一個字元串,或ArrayBuffer,或 Stream 14 transformRequest: [function(data, headers) { 15 // 對 data 進行任意轉換處理 16 return data; 17 }] 18 19 // 'transformResponse' 在傳遞給 then/catch前, 允許修改響應數據 20 transformResponse:[function(data) { 21 // 對 data 進行任意轉換處理 22 return data 23 }] 24 25 // 'headers' 是即將被發送的自定義請求頭 26 headers: { 'X-Requested-With': 'XMLHttpRequest' } 27 28 // 'params' 是即將與請求一起發送的URL參數 29 // 必須是一個無格式對象(plain object) 或 URLSearchParams對象 30 params: { 31 ID: 12345 32 } 33 34 // 'paramsSerializer'是一個負責 'params' 序列化的函數 35 paramsSerializer: function(params) { 36 return Qs.stringify(params, {arrayFormat: 'brackets'}) 37 } 38 39 // 'data' 是作為請求主體被發送的數據 40 // 只適用於這些請求方法 PUT POST PATHCH 41 // 在沒有設置 `transformRequest` 時, 必須以下類型之一: 42 // - string,plain object, ArrayBuffer,ArrayBufferView,URLSearchParams 43 // - 瀏覽器專屬: FormData, File, Blob 44 // - Node 專屬: Stream 45 data: { 46 firstName: 'Fred' 47 } 48 49 // 指定請求超時的毫秒數(0表示無超時時間) 50 // 請求超時,請求將被中斷 51 timeout: 1000 52 53 // 'withCreadentials' 表示跨越請求時是否需要使用憑證 54 withCreadentials: true, // default -> false 55 56 // 'adapter' 允許自定義處理請求,以使測試更輕鬆 57 adapter: function(config) { 58 /* */ 59 } 60 61 // 'auth' 表示應該使用 HTTP 基礎驗證,並提供憑證 62 // 這將設置一個'Authorization' 頭,覆寫掉現有的任意使用'hedaers'設置的自定義'Authorization' 63 auth: { 64 username: 'janedoe', 65 password: 's00pers3cret' 66 } 67 68 // 'responseType' 表示伺服器響應的數據類型, 可以是 'arraybuffer', 'blob', 'document', 'json' 69 responseType: 'json', 70 71 // 'xsrfCookieName' 是作用於xsrf token 的值得cookie的名稱 72 xsrfCookieName: 'XSRF-TOKEN' 73 74 // 'onUploadProgress' 允許為上傳處理進度事件 75 onUploadProgress: function(progressEvent) { 76 // Do whatever you want with the native progress event 77 } 78 79 // 'onDownloadProgress' 允許為下載處理進度事件 80 onDownloadProgress: function(progressEvent) { 81 // 對原生進度事件的處理 82 } 83 84 // 'maxContentLength' 定義允許的響應內容的最大尺寸 85 maxContentLength: 2000 86 87 // 'validateStatus' 定義對於給定的HTTP響應狀態碼是resolve 或 reject promis 88 validateStatus: function(status) { 89 return status >=200 && status <300 90 } 91 92 // 'maxRedirects' 定義在node.js中 follow的最大重定向數目 93 // 如果設置為0, 將不會 follow 任何重定向 94 maxRedirects: 5, // default 95 96 // 'proxy' 定義代理伺服器的主機名稱和埠 97 // 'auth' 表示HTTP 基礎驗證應當用於連接代理,並提供憑證 98 // 這將會設置一個 'Proxy-Authorization'頭,覆蓋掉已有的通過使用`header`設置的自定義 99 proxy: { 100 host: '127.0.01', 101 port: 9000, 102 auth: { 103 username: 'mikeymike', 104 password: 'rapunz3l' 105 } 106 } 107 108 // 'cancelToken' 指定用於取消請求的 cancel token 109 cancelToken: new CancelToken(function(cancel) { 110 111 }) 112 }
2.響應結構
某個請求的響應包含以下資訊
1 { 2 // 'data' 由伺服器提供的響應 3 data: {} 4 5 // 'status' 來自伺服器響應的 HTTP 狀態資訊 6 status: 200, 7 8 // 'statusText' 來自伺服器響應的HTTP 狀態資訊 9 statusText: 'OK' 10 11 // 'headers' 伺服器響應的頭 12 headers: {} 13 14 // 'config' 是為請求提供的配置資訊 15 config: {} 16 17 request: {} 18 }
3.axios特點
1.基於promise的非同步ajax請求庫。
2.瀏覽器端/ node端都可以使用。
3.支援請求/ 響應攔截器。
4.支援取消請求。
5.請求/ 響應數據轉換。
6.批量發送多個請求。
4.axios.create(config)
1.根據指定配置創建一個新的axios,也就是每個新axios都有自己的配置
2.新axios只是沒有取消請求和批量發請求的方法,其他所有語法都是一致的
3.為什麼要設計這個語法?
(1) 需要:項目中有部分按介面需要的配置與另一部分介面需求的配置不太一樣,如何處理?
(2) 解決:創建2個新axios,每個人都有特有的配置,分別應用到不同要求的介面請求中
5.axios的處理鏈流程
1 // 添加請求攔截器(回調函數) -> 後添加先執行 2 axios.interceptors.request.use( 3 config => { 4 return config 5 }, 6 error => { 7 return Promise.reject(error) 8 } 9 ) 10 11 // 添加響應攔截器 12 axios.interceptors.response.use( 13 response => { 14 return response 15 }, 16 error => { 17 return Promise.reject(error) 18 } 19 )
6.取消請求
1 let cancel // 用於保存取消請求的函數 2 getProducts() { 3 // 準備發請求前,取消未完成的請求 4 if( typeof cancel === 'function' ) { 5 cancel('取消請求') 6 } 7 axios({ 8 url: '//localhost:8000/products1', 9 cancelToken: new axios.CancelToken((c) => { // c是用於取消當前請求的函數 10 // 保存取消函數,用於之後可能需要取消當前請求 11 cancel = c; 12 }) 13 }).then( 14 response => { 15 cancel = null 16 consoel.log('請求成功了') 17 }, 18 error => { 19 if(axios.isCancel(error)) { 20 console.log('取消請求的錯誤') 21 } else { // 請求出錯 22 cancel = null 23 console.log(error.message) 24 } 25 } 26 ) 27 } 28 29 cancelReq() { 30 if(type cancel === 'function') { 31 cancel('強製取消請求') 32 } else { 33 console.log('沒有可取消的請求') 34 } 35 } 36 // 調用 37 cancelReq()
axios源碼閱讀
1.文件目錄
dist ->打包生成後的文件
examples -> 一些例子
lib -> 核心文件程式碼
adapters -> 請求相關的文件夾
http.js -> node服務端發送http請求
xhr.js -> 真正發請求的模組
Cancel -> 取消請求相關的文件夾
Cancel.js -> 定義的是Cancel構造函數
CancelToken.js -> 定義的是一個構造函數,執行取消相關的
isCancel.js -> 判斷一個error是不是cancel類型錯誤
core -> 核心的一些東西
Axios.js -> Axios構造函數
dispathRequest.js -> 分發請求
InterceptorManager.js -> 攔截器相關
helpers ->工具模組
axios.js -> 向外暴露axios函數
defaults.js -> 默認配置相關的東西
index.js -> 外層的入口文件
2.axios與Axios的關係
①、從語法上來說:axios不是Axios的實例。
②、從功能上來說:axios是Axios的實例。(1.有自身的屬性。2.有他原型鏈上的方法)
③、axios是Axios.prototype函數bind()返回的函數
④、axios作為對象有Axios原型對象上的所有方法,有Axios對象上所有屬性
源碼中axios.js文件
1 /** 2 * Create an instance of Axios 3 * 4 * @param {Object} defaultConfig The default config for the instance 5 * @return {Axios} A new instance of Axios 6 */ 7 function createInstance(defaultConfig) { 8 var context = new Axios(defaultConfig); 9 // 等同於 Axios.prototype.request.bind(context) 10 // bind返回一個新函數,這個新函數內部會調用這個request函數, 11 // 簡單的理解:axios函數最終找request執行 12 // context是Axios的實例,一旦使用了request函數裡面的this指向的就是Axios實例 13 var instance = bind(Axios.prototype.request, context); // axios 14 15 // 將Axios原型對象上的方法拷貝到 instance上:request()/get()/post()/put()/delete() 16 utils.extend(instance, Axios.prototype, context); 17 18 // Copy context to instance 19 // 將Axios實例對象上的屬性拷貝到instance上:defaults和interceptors屬性 20 utils.extend(instance, context); 21 22 return instance; 23 } 24 25 // Create the default instance to be exported 26 var axios = createInstance(defaults);
3.instance與axios的區別?
1.相同:
(1)都是一個能發任意請求的函數: request(config)
(2)都有發特定請求的各種方法:get()/post()/put()/delete()
(3)都有默認配置和攔截器屬性:defaults/interceptors
2.不同:
(1)默認匹配的值很可能不一樣
(2)instance沒有axios後面添加的一些方法:create()/CancelToken()/all()
源碼中axios.js文件
1 // Factory for creating new instances 2 axios.create = function create(instanceConfig) { 3 // 還是調用了createInstance方法,跟上面一樣再走裡面一遍 4 return createInstance(mergeConfig(axios.defaults, instanceConfig)); 5 }; 6 7 // Expose Cancel & CancelToken 8 // 不一樣的地方是axios添加了這些,而axios.create並沒有添加這些操作 9 axios.Cancel = require('./cancel/Cancel'); 10 axios.CancelToken = require('./cancel/CancelToken'); 11 axios.isCancel = require('./cancel/isCancel'); 12 13 // Expose all/spread 14 axios.all = function all(promises) { 15 return Promise.all(promises); 16 };
4.axios運行的整體流程?
主要流程:
request(config) == > dispatchRequest(config) ===> xhrAdapter(config)
request(config):
將請求攔截器 / dispatchRequest() / 響應攔截器 通過promise鏈串起來,然後promise
dispatchRequest(config) :
轉換請求數據 ===> 調用xhrAdapter()發請求 ===> 請求返回後轉換響應數據.返回promise
xhrAdapter(config):
創建XHR對象,根據config進行相應設置,發送特定請求,並接收響應數據,返回promise。
axiso流程圖:
request(config)
源碼axios.js / Axios.js文件
Promise通過它的鏈使用將請求攔截器,發請求的操作,響應攔截器,以及我們的最後請求的成功失敗串聯起來
1 /** 2 * Create an instance of Axios 3 * 4 * @param {Object} defaultConfig The default config for the instance 5 * @return {Axios} A new instance of Axios 6 */ 7 function createInstance(defaultConfig) { 8 var context = new Axios(defaultConfig); 9 // Axios.prototype.request.bind(context) 10 var instance = bind(Axios.prototype.request, context); // axios 11 12 // Copy axios.prototype to instance 13 // 將Axios原型對象上的方法拷貝到 instance上:request()/get()/post()/put()/delete() 14 utils.extend(instance, Axios.prototype, context); 15 16 // Copy context to instance 17 // 將Axios實例對象上的屬性拷貝到instance上:defaults和interceptors屬性 18 utils.extend(instance, context); 19 20 return instance; 21 } 22 23 // Create the default instance to be exported 24 var axios = createInstance(defaults); 25 26 // Expose Axios class to allow class inheritance 27 axios.Axios = Axios; 28 29 // Factory for creating new instances 30 axios.create = function create(instanceConfig) { 31 return createInstance(mergeConfig(axios.defaults, instanceConfig)); 32 };
1 /** 2 * 主要用於發請求的函數 3 * 我們使用的axios就是此函數bind()返回的函數 4 * 5 * Dispatch a request 6 * 7 * @param {Object} config The config specific for this request (merged with this.defaults) 8 */ 9 Axios.prototype.request = function request(config) { 10 /*eslint no-param-reassign:0*/ 11 // Allow for axios('example/url'[, config]) a la fetch API 12 if (typeof config === 'string') { 13 config = arguments[1] || {}; 14 config.url = arguments[0]; 15 } else { 16 config = config || {}; 17 } 18 19 config = mergeConfig(this.defaults, config); 20 21 // Set config.method 22 if (config.method) { 23 config.method = config.method.toLowerCase(); 24 } else if (this.defaults.method) { 25 config.method = this.defaults.method.toLowerCase(); 26 } else { 27 config.method = 'get'; 28 } 29 30 ------ 31 // Promise通過它的鏈使用將請求攔截器,發請求的操作,響應攔截器,以及我們的最厚請求的成功失敗串聯起來 32 33 // Hook up interceptors middleware 34 // undefined作用:chain後面是兩兩一組進行調用,有了它作為發請求那一組的reject失敗回調,保證了 響應攔截器還是被兩兩一組返回調用,避免錯位 35 var chain = [dispatchRequest, undefined]; 36 var promise = Promise.resolve(config); 37 38 // 找到所有的請求攔截器函數,後添加的請求攔截器保存在數組的前面 39 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { 40 chain.unshift(interceptor.fulfilled, interceptor.rejected); 41 }); 42 43 // 後添加的響應攔截器保存在數組後面 44 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { 45 chain.push(interceptor.fulfilled, interceptor.rejected); 46 }); 47 48 // 通過promise的then()串連起來所有的請求攔截器/請求方法/ 響應攔截器 49 while (chain.length) { 50 promise = promise.then(chain.shift(), chain.shift()); 51 } 52 53 // 返回用來指定我們的onResolved和onRejected的promise 54 return promise; 55 };
dispatchRequest(config)
源碼dispatchRequest.js/default.js文件
1 /** 2 * Dispatch a request to the server using the configured adapter. 3 * 4 * @param {object} config The config that is to be used for the request 5 * @returns {Promise} The Promise to be fulfilled 6 */ 7 module.exports = function dispatchRequest(config) { 8 throwIfCancellationRequested(config); 9 10 // Ensure headers exist 11 config.headers = config.headers || {}; 12 13 // 對config中的data進行必要的處理轉換 14 // 設置相應的Content-Type請求頭 15 // Transform request data 16 config.data = transformData( 17 config.data, 18 config.headers, 19 // 轉換數據格式 20 config.transformRequest // --對應在defalut文件中 21 ); 22 23 // Flatten headers 24 // 整合config中所有的header 25 config.headers = utils.merge( 26 config.headers.common || {}, 27 config.headers[config.method] || {}, 28 config.headers 29 ); 30 31 utils.forEach( 32 ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], 33 function cleanHeaderConfig(method) { 34 delete config.headers[method]; 35 } 36 ); 37 38 var adapter = config.adapter || defaults.adapter; 39 40 return adapter(config).then(function onAdapterResolution(response) { 41 throwIfCancellationRequested(config); 42 43 // Transform response data 44 // 對response中還沒有解析的data數據進行解析 45 // Json字元串解析為js對象/數組 46 response.data = transformData( 47 response.data, 48 response.headers, 49 // 轉換數據格式 50 config.transformResponse // --對應在defalut文件中 51 ); 52 53 return response; 54 }, function onAdapterRejection(reason) { 55 if (!isCancel(reason)) { 56 throwIfCancellationRequested(config); 57 58 // Transform response data 59 if (reason && reason.response) { 60 reason.response.data = transformData( 61 reason.response.data, 62 reason.response.headers, 63 config.transformResponse 64 ); 65 } 66 } 67 68 return Promise.reject(reason); 69 }); 70 };
1 // 得到當前環境對應的請求適配器 2 adapter: getDefaultAdapter(), 3 4 // 請求轉換器 5 transformRequest: [function transformRequest(data, headers) { 6 normalizeHeaderName(headers, 'Accept'); 7 normalizeHeaderName(headers, 'Content-Type'); 8 if (utils.isFormData(data) || 9 utils.isArrayBuffer(data) || 10 utils.isBuffer(data) || 11 utils.isStream(data) || 12 utils.isFile(data) || 13 utils.isBlob(data) 14 ) { 15 return data; 16 } 17 if (utils.isArrayBufferView(data)) { 18 return data.buffer; 19 } 20 if (utils.isURLSearchParams(data)) { 21 setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); 22 return data.toString(); 23 } 24 // 如果data是對象,指定請求體參數格式為json,並將參數數據對象轉換為json 25 if (utils.isObject(data)) { 26 setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); 27 return JSON.stringify(data); 28 } 29 return data; 30 }], 31 32 // 響應數據轉換器:解析字元串類型的data數據 33 transformResponse: [function transformResponse(data) { 34 /*eslint no-param-reassign:0*/ 35 if (typeof data === 'string') { 36 try { 37 data = JSON.parse(data); 38 } catch (e) { /* Ignore */ } 39 } 40 return data; 41 }],
xhrAdapter(config)
源碼xhr.js文件
1 // 創建XHR對象 2 var request = new XMLHttpRequest(); 3 4 // HTTP basic authentication 5 if (config.auth) { 6 var username = config.auth.username || ''; 7 var password = unescape(encodeURIComponent(config.auth.password)) || ''; 8 requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); 9 } 10 11 var fullPath = buildFullPath(config.baseURL, config.url); 12 13 // 初始化請求 14 request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); 15 // buildURL在help文件夾buildURL.js處理請求url 16 17 // Set the request timeout in MS 18 // 指定超時的時間 19 request.timeout = config.timeout; 20 21 // Listen for ready state 22 // 綁定請求狀態改變的監聽 23 request.onreadystatechange = function handleLoad() { 24 if (!request || request.readyState !== 4) { 25 return; 26 } 27 28 // The request errored out and we didn't get a response, this will be 29 // handled by onerror instead 30 // With one exception: request that using file: protocol, most browsers 31 // will return status as 0 even though it's a successful request 32 if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { 33 return; 34 } 35 36 // Prepare the response 37 // 準備response對象 38 var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; 39 var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; 40 var response = { 41 data: responseData, 42 status: request.status, 43 statusText: request.statusText, 44 headers: responseHeaders, 45 config: config, 46 request: request 47 }; 48 49 // 根據響應狀態碼來確定請求的promise的結果狀態(成功/失敗) 50 settle(resolve, reject, response); // 下方settle.js文件 51 52 // Clean up request 53 request = null; 54 }; 55 56 // Handle browser request cancellation (as opposed to a manual cancellation) 57 // 綁定請求中斷監聽 58 request.onabort = function handleAbort() { 59 if (!request) { 60 return; 61 } 62 63 reject(createError('Request aborted', config, 'ECONNABORTED', request)); 64 65 // Clean up request 66 request = null; 67 }; 68 69 // Not all browsers support upload events 70 // 綁定上傳進度的監聽 71 if (typeof config.onUploadProgress === 'function' && request.upload) { 72 request.upload.addEventListener('progress', config.onUploadProgress); 73 } 74 75 // 如果配置了cancelToken 76 if (config.cancelToken) { 77 // Handle cancellation 78 // 指定用於中斷請求的回調函數 79 config.cancelToken.promise.then(function onCanceled(cancel) { 80 if (!request) { 81 return; 82 } 83 84 // 中斷請求 85 request.abort(); 86 // 讓請求的promise失敗 87 reject(cancel); 88 // Clean up request 89 request = null; 90 }); 91 } 92 93 if (!requestData) { 94 requestData = null; 95 } 96 97 // Send the request 98 // 發送請求,指定請求體數據,可能是null 99 request.send(requestData);
settle.js文件
1 /** 2 * Resolve or reject a Promise based on response status. 3 * 4 * @param {Function} resolve A function that resolves the promise. 5 * @param {Function} reject A function that rejects the promise. 6 * @param {object} response The response. 7 */ 8 module.exports = function settle(resolve, reject, response) { 9 var validateStatus = response.config.validateStatus; 10 if (!response.status || !validateStatus || validateStatus(response.status)) { 11 // 請求成功 12 resolve(response); 13 } else { 14 // 請求出錯 15 reject(createError( // 源碼createError.js內 創建error對象的函數 16 'Request failed with status code ' + response.status, 17 response.config, 18 null, 19 response.request, 20 response 21 )); 22 } 23 }; 24 25 default.js文件 26 27 // 判讀狀態碼的合法性: [200,299] 28 validateStatus: function validateStatus(status) { 29 return status >= 200 && status < 300; 30 }
5.axios的請求/響應數據轉換器是什麼?
函數
1.請求轉換器:對請求頭和請求體數據進行特定處理的函數
1 // 如果data是對象,指定請求體參數格式為json,並將參數數據對象轉換為json 2 if (utils.isObject(data)) { 3 setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); 4 return JSON.stringify(data); 5 }
2.響應轉換器: 將響應體json字元串解析為js對象或數組的函數
1 if (typeof data === 'string') { 2 try { 3 data = JSON.parse(data); 4 } catch (e) { /* Ignore */ } 5 }
6.response的整體結構
1 { 2 data, 3 status, 4 statusText, 5 headers, 6 config, 7 request 8 }; 9 // 對應源碼adapters文件夾的xhr.js
7.error整體結構
1 { 2 config, 3 request, 4 response, 5 ... 6 } 7 // 對應源碼core文件夾的enhanceError.js
8.如何取消未完成的請求?
1.當配置了cancelToken對象時,保存cancel函數
(1) 創建一個用於將來中斷請求的cancelPromise
(2) 並定義了一個用於取消請求的cancel函數
(3)將cancel函數傳遞出來
2.調用cancel()取消請求
(1)執行cancel函數傳入錯誤資訊message
(2)內部會讓cancelPromise變為成功,且成功的值為一個Cancel對象
(3)在cancelPromise的成功回調中中斷請求,並讓發請求的promise失敗,失敗的reason為Cancel對象
源碼CancelToken.js / xhr.js
1 function CancelToken(executor) { 2 if (typeof executor !== 'function') { 3 throw new TypeError('executor must be a function.'); 4 } 5 6 // 為取消請求準備一個promise對象,並保存resolve函數到外部(外部有機會可以使用promise) 7 var resolvePromise; 8 this.promise = new Promise(function promiseExecutor(resolve) { 9 resolvePromise = resolve; 10 }); 11 12 // 保存當前token對象 13 var token = this; 14 // 執行器函數是外部定義,內部調用 15 // 立即執行接收的執行器函數,並傳入用於取消請求的cancel函數 16 // cancel函數是內部定義,外部調用 -》結合上面(axios理解使用第6點一起康康) 17 executor(function cancel(message) { 18 // 如果token中有reason了,說明請求已取消。 | 這個if後看,先看下面的token.reason 19 if (token.reason) { 20 // Cancellation has already been requested 21 return; 22 } 23 24 // 將token的reason指定為一個Canel對象 25 token.reason = new Cancel(message); 26 // 將取消請求的promise指定為成功,值為reason 27 resolvePromise(token.reason); 28 }); 29 }
// 如果配置了cancelToken if (config.cancelToken) { // Handle cancellation // 指定用於中斷請求的回調函數 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { // 如果請求還沒有結束,可以走後面的中斷請求 // 如果請求結束了,直接return,無法走後面的中斷請求一系列操作 return; } // 中斷請求 request.abort(); // 讓請求的promise失敗 reject(cancel); // Clean up request request = null; }); }
HTTP相關、XHR相關具體源碼內部注釋歡迎查看Github。
或者進入我的部落格專欄查閱。