自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官不要再問(wèn)我 axios 了?我能手寫(xiě)簡(jiǎn)易版的 axios

開(kāi)發(fā) 前端
作為我們工作中的常用的ajax請(qǐng)求庫(kù),作為前端工程師的我們當(dāng)然是想一探究竟,axios究竟是如何去架構(gòu)整個(gè)框架,中間的攔截器、適配器、 取消請(qǐng)求這些都是我們經(jīng)常使用的。

 [[436491]]

作為我們工作中的常用的ajax請(qǐng)求庫(kù),作為前端工程師的我們當(dāng)然是想一探究竟,axios究竟是如何去架構(gòu)整個(gè)框架,中間的攔截器、適配器、 取消請(qǐng)求這些都是我們經(jīng)常使用的。

前言

由于axios源碼中有很多不是很重要的方法,而且很多方法為了考慮兼容性,并沒(méi)有考慮到用es6 的語(yǔ)法去寫(xiě)。本篇主要是帶你去梳理axios的主要流程,并用es6重寫(xiě)簡(jiǎn)易版axios

  •  攔截器
  •  適配器
  •  取消請(qǐng)求

攔截器

一個(gè)axios實(shí)例上有兩個(gè)攔截器,一個(gè)是請(qǐng)求攔截器, 然后響應(yīng)攔截器。我們下看下官網(wǎng)的用法:添加攔截器 

  1. // 添加請(qǐng)求攔截器  
  2. axios.interceptors.request.use(function (config) {  
  3.     // 在發(fā)送請(qǐng)求之前做些什么  
  4.     return config;  
  5.   }, function (error) {  
  6.     // 對(duì)請(qǐng)求錯(cuò)誤做些什么  
  7.     return Promise.reject(error);  
  8.   }); 

移除攔截器 

  1. const myInterceptor = axios.interceptors.request.use(function () {/*...*/});  
  2. axios.interceptors.request.eject(myInterceptor); 

其實(shí)源碼中就是,所有攔截器的執(zhí)行 所以說(shuō)肯定有一個(gè)forEach方法。

思路理清楚了,現(xiàn)在我們就開(kāi)始去寫(xiě)吧。代碼我就直接發(fā)出來(lái),然后我在下面注解。 

  1. export class InterceptorManager {  
  2.   constructor() {  
  3.     // 存放所有攔截器的棧  
  4.     this.handlers = []  
  5.   }  
  6.   use(fulfilled, rejected) {  
  7.     this.handlers.push({  
  8.       fulfilled,  
  9.       rejected,  
  10.     })  
  11.     //返回id 便于取消  
  12.     return this.handlers.length - 1  
  13.   } 
  14.   // 取消一個(gè)攔截器  
  15.   eject(id) {  
  16.     if (this.handlers[id]) {  
  17.       this.handlers[id] = null  
  18.     }  
  19.   }  
  20.   // 執(zhí)行棧中所有的hanlder  
  21.   forEach(fn) {  
  22.     this.handlers.forEach((item) => {  
  23.       // 這里為了過(guò)濾已經(jīng)被取消的攔截器,因?yàn)橐呀?jīng)取消的攔截器被置null  
  24.       if (item) {  
  25.         fn(item)  
  26.       }  
  27.     })  
  28.   }  

攔截器這個(gè)類(lèi)我們已經(jīng)初步實(shí)現(xiàn)了,現(xiàn)在我們?nèi)?shí)現(xiàn)axios 這個(gè)類(lèi),還是先看下官方文檔,先看用法,再去分析。

axios(config) 

  1. // 發(fā)送 POST 請(qǐng)求  
  2. axios({  
  3.   method: 'post',  
  4.   url: '/user/12345',  
  5.   data: {  
  6.     firstName: 'Fred',  
  7.     lastName: 'Flintstone'  
  8.   }  
  9. }); 

axios(url[, config]) 

  1. // 發(fā)送 GET 請(qǐng)求(默認(rèn)的方法)  
  2. axios('/user/12345'); 

Axios 這個(gè)類(lèi)最核心的方法其實(shí)還是 request 這個(gè)方法。我們先看下實(shí)現(xiàn)吧!

  1. class Axios {  
  2.   constructor(config) {  
  3.     this.defaults = config  
  4.     this.interceptors = {  
  5.       request: new InterceptorManager(),  
  6.       response: new InterceptorManager(),  
  7.     }  
  8.   }  
  9.   // 發(fā)送一個(gè)請(qǐng)求  
  10.   request(config) {  
  11.     // 這里呢其實(shí)就是去處理了 axios(url[,config])  
  12.     if (typeof config == 'string') {  
  13.       config = arguments[1] || {}  
  14.       config.url = arguments[0]  
  15.     } else {  
  16.       configconfig = config || {}  
  17.     }  
  18.     // 默認(rèn)get請(qǐng)求,并且都轉(zhuǎn)成小寫(xiě)  
  19.     if (config.method) {  
  20.       configconfig.method = config.method.toLowerCase()  
  21.     } else {  
  22.       config.method = 'get'  
  23.     }  
  24.     // dispatchRequest 就是發(fā)送ajax請(qǐng)求  
  25.     const chain = [dispatchRequest, undefined]  
  26.     //  發(fā)生請(qǐng)求之前加入攔截的 fulfille 和reject 函數(shù)  
  27.     this.interceptors.request.forEach((item) => {  
  28.       chain.unshift(item.fulfilled, item.rejected)  
  29.     })  
  30.     // 在請(qǐng)求之后增加 fulfilled 和reject 函數(shù)  
  31.     this.interceptors.response.forEach((item) => {  
  32.       chain.push(item.fulfilled, item.rejected)  
  33.     })  
  34.     // 利用promise的鏈?zhǔn)秸{(diào)用,將參數(shù)一層一層傳下去  
  35.     let promise = Promise.resolve(config)  
  36.     //然后我去遍歷 chain  
  37.     while (chain.length) {  
  38.       // 這里不斷出棧 直到結(jié)束為止  
  39.       promisepromise = promise.then(chain.shift(), chain.shift())  
  40.     }  
  41.     return promise  
  42.   }  

這里其實(shí)就是體現(xiàn)了axios設(shè)計(jì)的巧妙, 維護(hù)一個(gè)棧結(jié)構(gòu) + promise 的鏈?zhǔn)秸{(diào)用 實(shí)現(xiàn)了 攔截器的功能, 可能有的小伙伴到這里還是不是很能理解,我還是給大家畫(huà)一個(gè)草圖去模擬下這個(gè)過(guò)程。

假設(shè)我有1個(gè)請(qǐng)求攔截器handler和1個(gè)響應(yīng)攔截器handler

一開(kāi)始我們棧中的數(shù)據(jù)就兩個(gè)

這個(gè)沒(méi)什么問(wèn)題,由于有攔截器的存在,如果存在的話(huà),那么我們就要往這個(gè)棧中加數(shù)據(jù),請(qǐng)求攔截器顧名思義要在請(qǐng)求之前所以是unshift。加完請(qǐng)求攔截器我們的棧變成了這樣。

沒(méi)什么問(wèn)題,然后請(qǐng)求結(jié)束后,我們又想對(duì)請(qǐng)求之后的數(shù)據(jù)做處理,所以響應(yīng)攔截的數(shù)據(jù)自然是push了。這時(shí)候棧結(jié)構(gòu)變成了這樣:

然后遍歷整個(gè)棧結(jié)構(gòu),每次出棧都是一對(duì)出棧, 因?yàn)閜romise 的then 就是 一個(gè)成功,一個(gè)失敗嘛。遍歷結(jié)束后,返回經(jīng)過(guò)所有處理的promise,然后你就可以拿到最終的值了。

adapter

Adapter: 英文解釋是適配器的意思。這里我就不實(shí)現(xiàn)了,我?guī)Т蠹铱匆幌略创a。adapter 做了一件事非常簡(jiǎn)單,就是根據(jù)不同的環(huán)境 使用不同的請(qǐng)求。如果用戶(hù)自定義了adapter,就用config.adapter。否則就是默認(rèn)是default.adpter。

  1. var adapter = config.adapter || defaults.adapter;  
  2. return adapter(config).then() ...  

繼續(xù)往下看deafults.adapter做了什么事情: 

  1. function getDefaultAdapter() {  
  2.   var adapter;  
  3.   if (typeof XMLHttpRequest !== 'undefined') {  
  4.     // For browsers use XHR adapter  
  5.     adapter = require('./adapters/xhr');  
  6.   } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {  
  7.     // For node use HTTP adapter  
  8.     adapter = require('./adapters/http');  
  9.   }  
  10.   return adapter;  

其實(shí)就是做個(gè)選擇:如果是瀏覽器環(huán)境:就是用xhr 否則就是node 環(huán)境。判斷process是否存在。從寫(xiě)代碼的角度來(lái)說(shuō),axios源碼的這里的設(shè)計(jì)可擴(kuò)展性非常好。有點(diǎn)像設(shè)計(jì)模式中的適配器模式, 因?yàn)闉g覽器端和node 端 發(fā)送請(qǐng)求其實(shí)并不一樣, 但是我們不重要,我們不去管他的內(nèi)部實(shí)現(xiàn),用promise包一層做到對(duì)外統(tǒng)一。所以 我們用axios 自定義adapter 器的時(shí)候, 一定是返回一個(gè)promise。ok請(qǐng)求的方法我在下面模擬寫(xiě)出。

cancleToken

我首先問(wèn)大家一個(gè)問(wèn)題,取消請(qǐng)求原生瀏覽器是怎么做到的?有一個(gè)abort 方法??梢匀∠?qǐng)求。那么axios源碼肯定也是運(yùn)用了這一點(diǎn)去取消請(qǐng)求?,F(xiàn)在瀏覽器其實(shí)也支持fetch請(qǐng)求, fetch可以取消請(qǐng)求?很多同學(xué)說(shuō)是不可以的,其實(shí)不是?fetch 結(jié)合 abortController 可以實(shí)現(xiàn)取消fetch請(qǐng)求。我們看下例子: 

  1. const controller = new AbortController();  
  2. const { signal } = controller;  
  3. fetch("http://localhost:8000", { signal }).then(response => {  
  4.     console.log(`Request 1 is complete!`);  
  5. }).catch(e => {  
  6.     console.warn(`Fetch 1 error: ${e.message}`);  
  7. });  
  8. // Wait 2 seconds to abort both requests  
  9. setTimeout(() => controller.abort(), 2000); 

但是這是個(gè)實(shí)驗(yàn)性功能,可惡的ie。所以我們這次還是用原生的瀏覽器xhr基于promise簡(jiǎn)單的封裝一下。代碼如下: 

  1. export function dispatchRequest(config) {  
  2.   return new Promise((resolve, reject) => {  
  3.     const xhr = new XMLHttpRequest()  
  4.     xhr.open(config.method, config.url)  
  5.     xhr.onreadystatechange = function () {  
  6.       if (xhr.status >= 200 && xhr.status <= 300 && xhr.readyState === 4) {  
  7.         resolve(xhr.responseText)  
  8.       } else {  
  9.         reject('失敗了')  
  10.       }  
  11.     }  
  12.     if (config.cancelToken) {  
  13.       // Handle cancellation  
  14.       config.cancelToken.promise.then(function onCanceled(cancel) {  
  15.         if (!xhr) {  
  16.           return 
  17.         }  
  18.         xhr.abort()  
  19.         reject(cancel)  
  20.         // Clean up request  
  21.         xhr = null  
  22.       }) 
  23.     }  
  24.     xhr.send()  
  25.   })  

Axios 源碼里面做了很多處理, 這里我只做了get處理,我主要的目的就是為了axios是如何取消請(qǐng)求的。先看下官方用法:

主要是兩種用法:

使用 cancel token 取消請(qǐng)求 

  1. const CancelToken = axios.CancelToken;  
  2. const source = CancelToken.source();  
  3. axios.get('/user/12345', {  
  4.   cancelToken: source.token  
  5. }).catch(function(thrown) {  
  6.   if (axios.isCancel(thrown)) {  
  7.     console.log('Request canceled', thrown.message);  
  8.   } else {  
  9.      // 處理錯(cuò)誤  
  10.   }  
  11. });  
  12. axios.post('/user/12345', {  
  13.   name: 'new name'  
  14. }, {  
  15.   cancelToken: source.token  
  16. })  
  17. // 取消請(qǐng)求(message 參數(shù)是可選的)  
  18. source.cancel('Operation canceled by the user.'); 

還可以通過(guò)傳遞一個(gè) executor 函數(shù)到 CancelToken 的構(gòu)造函數(shù)來(lái)創(chuàng)建 cancel token: 

  1. const CancelToken = axios.CancelToken;  
  2. let cancel;  
  3. axios.get('/user/12345', {  
  4.   cancelToken: new CancelToken(function executor(c) {  
  5.     // executor 函數(shù)接收一個(gè) cancel 函數(shù)作為參數(shù)  
  6.     ccancel = c;  
  7.   })  
  8. });  
  9. // cancel the request  
  10. cancel(); 

看了官方用法 和結(jié)合axios源碼,我給出以下實(shí)現(xiàn): 

  1. export class cancelToken {  
  2.     constructor(exactor) {  
  3.         if (typeof executor !== 'function') {  
  4.         throw new TypeError('executor must be a function.')  
  5.         }  
  6.         // 這里其實(shí)將promise的控制權(quán) 交給 cancel 函數(shù)  
  7.         // 同時(shí)做了防止多次重復(fù)cancel 之前 Redux 還有React 源碼中也有類(lèi)似的案列  
  8.         const resolvePromise;  
  9.         this.promise =  new Promise(resolve => {  
  10.             resolveresolvePromise = resolve;  
  11.         })  
  12.         this.reason = undefined      
  13.          const cancel  = (message) => {  
  14.             if(this.reason) {  
  15.                 return;  
  16.             }  
  17.             this.reason = 'cancel' + message;  
  18.             resolvePromise(this.reason);  
  19.         }  
  20.         exactor(cancel)  
  21.     }  
  22.     throwIfRequested() {  
  23.         if(this.reason) {  
  24.             throw this.reason  
  25.         }  
  26.     }   
  27.      // source 其實(shí)本質(zhì)上是一個(gè)語(yǔ)法糖 里面做了封裝  
  28.     static source() {  
  29.         const cancel;  
  30.         const token = new cancelToken(function executor(c) {  
  31.             ccancel = c;  
  32.         });  
  33.         return {  
  34.             token: token,  
  35.             cancel: cancel  
  36.         };  
  37.     }  

截止到這里大體axios 大體功能已經(jīng)給出。

接下來(lái)我就測(cè)試下我的手寫(xiě)axios,有沒(méi)有什么問(wèn)題? 

  1. <script type="module" >  
  2.    import Axios from './axios.js';  
  3.    const config = { url:'http://101.132.113.6:3030/api/mock' }  
  4.    const axios =  new Axios();  
  5.    axios.request(config).then(res => {  
  6.        console.log(res,'0000')  
  7.    }).catch(err => { 
  8.       console.log(err)  
  9.    })  
  10. /script> 

打開(kāi)瀏覽器看一下結(jié)果:

成功了ok, 然后我來(lái)測(cè)試一下攔截器的功能,代碼更新成下面這樣: 

  1. import Axios from './axios.js';  
  2. const config = { url:'http://101.132.113.6:3030/api/mock' }  
  3. const axios =  new Axios();  
  4. // 在axios 實(shí)例上掛載屬性  
  5. const err = () => {}  
  6. axios.interceptors.request.use((config)=> {  
  7.     console.log('我是請(qǐng)求攔截器1')  
  8.     config.id = 1 
  9.     return  config  
  10. },err )  
  11. axios.interceptors.request.use((config)=> {  
  12.     config.id = 2  
  13.     console.log('我是請(qǐng)求攔截器2')  
  14.     return config  
  15. },err)  
  16. axios.interceptors.response.use((data)=> {  
  17.     console.log('我是響應(yīng)攔截器1',data )  
  18.     data += 1;  
  19.     return data;  
  20. },err)  
  21. axios.interceptors.response.use((data)=> {  
  22.     console.log('我是響應(yīng)攔截器2',data )  
  23.     return  data  
  24. },err)  
  25. axios.request(config).then(res => {  
  26.     // console.log(res,'0000')  
  27.     // return res;  
  28. }).catch(err => {  
  29.     console.log(err)  
  30. })  console.log(err)}) 

ajax 請(qǐng)求的結(jié)果 我是resolve(1) ,所以我們看下輸出路徑:

沒(méi)什么問(wèn)題, 響應(yīng)后的數(shù)據(jù)我加了1。

接下來(lái)我來(lái)是取消請(qǐng)求的兩種方式 :

  1. // 第一種方式  
  2. let  cancelFun = undefined 
  3. const cancelInstance = new cancelToken((c)=> 
  4.     ccancelFun = c;  
  5. });  
  6. config.cancelToken = cancelInstance 
  7. // 50 ms 就取消請(qǐng)求  
  8. setTimeout(()=> 
  9.     cancelFun('取消成功')  
  10. },50)  
  11. 第二種方式:  
  12. const { token, cancel }  = cancelToken.source();  
  13. config.cancelToken = token 
  14. setTimeout(()=> 
  15.     cancel()  
  16. },50) 

結(jié)果都是OK的,至此axios簡(jiǎn)單源碼終于搞定了。

反思

本篇文章只是把a(bǔ)xios源碼的大體流程走了一遍, axios源碼內(nèi)部還是做了很多兼容比如:配置優(yōu)先級(jí):他有一個(gè)mergeConfig 方法, 還有數(shù)據(jù)轉(zhuǎn)換器。不過(guò)這些不影響我們對(duì)axios源碼的整體梳理, 源碼中其實(shí)有一個(gè)createInstance,至于為什么有?我覺(jué)得就是為了可擴(kuò)展性更好, 將來(lái)有啥新功能,直接在原有axios的實(shí)例的原型鏈上去增加,代碼可維護(hù)性強(qiáng), axios.all spread 都是實(shí)例new出來(lái)再去掛的,不過(guò)都很簡(jiǎn)單,沒(méi)啥的。有興趣大家自行閱讀。 

 

責(zé)任編輯:龐桂玉 來(lái)源: 前端大全
相關(guān)推薦

2020-10-20 09:12:57

axios核心原理

2020-04-16 08:22:11

HTTPS加解密協(xié)議

2021-12-02 08:19:06

MVCC面試數(shù)據(jù)庫(kù)

2022-03-14 10:14:43

底層系統(tǒng)Nacos

2019-07-10 10:06:24

面試官三次握手四次揮手

2022-04-10 18:10:24

CURD鏈表

2018-09-28 05:25:53

TopK算法代碼

2020-12-01 11:50:49

數(shù)據(jù)庫(kù)Redis面試

2018-11-09 09:34:05

面試Spring Clou底層

2020-08-26 08:18:39

數(shù)據(jù)索引查詢(xún)

2022-01-10 11:04:41

單鏈表面試編程

2018-10-28 22:37:00

計(jì)數(shù)排序排序面試

2018-11-01 13:49:23

桶排序排序面試

2020-12-03 07:39:50

HashMap底層數(shù)據(jù)

2022-02-11 13:44:56

fiber架構(gòu)React

2020-11-24 07:48:32

React

2021-08-10 18:36:02

Express原理面試

2021-05-08 07:53:33

面試線(xiàn)程池系統(tǒng)

2022-05-24 08:03:28

InnoDBMySQL數(shù)據(jù)

2021-04-01 08:12:20

zookeeper集群源碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)