<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官不要再問我 axios 了?我能手寫簡易版的 axios

          共 6972字,需瀏覽 14分鐘

           ·

          2021-12-16 03:00

          點(diǎn)擊上方?前端Q,關(guān)注公眾號

          回復(fù)加群,加入前端Q技術(shù)交流群


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

          前言

          由于axios源碼中有很多不是很重要的方法,而且很多方法為了考慮兼容性,并沒有考慮到用es6 的語法去寫。本篇主要是帶你去梳理axios的主要流程,并用es6重寫簡易版axios

          • 攔截器

          • 適配器

          • 取消請求

          攔截器

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

          //?添加請求攔截器
          axios.interceptors.request.use(function?(config)?{
          ????//?在發(fā)送請求之前做些什么
          ????return?config;
          ??},?function?(error)?{
          ????//?對請求錯誤做些什么
          ????return?Promise.reject(error);
          ??});

          移除攔截器

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

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

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

          export?class?InterceptorManager?{
          ??constructor()?{
          ????//?存放所有攔截器的棧
          ????this.handlers?=?[]
          ??}

          ??use(fulfilled,?rejected)?{
          ????this.handlers.push({
          ??????fulfilled,
          ??????rejected,
          ????})
          ????//返回id?便于取消
          ????return?this.handlers.length?-?1
          ??}
          ??//?取消一個攔截器
          ??eject(id)?{
          ????if?(this.handlers[id])?{
          ??????this.handlers[id]?=?null
          ????}
          ??}

          ??//?執(zhí)行棧中所有的hanlder
          ??forEach(fn)?{
          ????this.handlers.forEach((item)?=>?{
          ??????//?這里為了過濾已經(jīng)被取消的攔截器,因為已經(jīng)取消的攔截器被置null
          ??????if?(item)?{
          ????????fn(item)
          ??????}
          ????})
          ??}
          }

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

          axios(config)

          //?發(fā)送?POST?請求
          axios({
          ??method:?'post',
          ??url:?'/user/12345',
          ??data:?{
          ????firstName:?'Fred',
          ????lastName:?'Flintstone'
          ??}
          });
          axios(url[, config])
          //?發(fā)送?GET?請求(默認(rèn)的方法)?
          axios('/user/12345');

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

          class?Axios?{
          ??constructor(config)?{
          ????this.defaults?=?config
          ????this.interceptors?=?{
          ??????request:?new?InterceptorManager(),
          ??????response:?new?InterceptorManager(),
          ????}
          ??}
          ??//?發(fā)送一個請求
          ??request(config)?{
          ????//?這里呢其實(shí)就是去處理了?axios(url[,config])
          ????if?(typeof?config?==?'string')?{
          ??????config?=?arguments[1]?||?{}
          ??????config.url?=?arguments[0]
          ????}?else?{
          ??????config?=?config?||?{}
          ????}

          ????//?默認(rèn)get請求,并且都轉(zhuǎn)成小寫
          ????if?(config.method)?{
          ??????config.method?=?config.method.toLowerCase()
          ????}?else?{
          ??????config.method?=?'get'
          ????}

          ????//?dispatchRequest?就是發(fā)送ajax請求
          ????const?chain?=?[dispatchRequest,?undefined]
          ????//??發(fā)生請求之前加入攔截的?fulfille?和reject?函數(shù)
          ????this.interceptors.request.forEach((item)?=>?{
          ??????chain.unshift(item.fulfilled,?item.rejected)
          ????})
          ????//?在請求之后增加?fulfilled?和reject?函數(shù)
          ????this.interceptors.response.forEach((item)?=>?{
          ??????chain.push(item.fulfilled,?item.rejected)
          ????})

          ????//?利用promise的鏈?zhǔn)秸{(diào)用,將參數(shù)一層一層傳下去
          ????let?promise?=?Promise.resolve(config)

          ????//然后我去遍歷?chain
          ????while?(chain.length)?{
          ??????//?這里不斷出棧?直到結(jié)束為止
          ??????promise?=?promise.then(chain.shift(),?chain.shift())
          ????}
          ????return?promise
          ??}
          }

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

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

          一開始我們棧中的數(shù)據(jù)就兩個

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

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

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

          adapter

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

          ?var?adapter?=?config.adapter?||?defaults.adapter;

          ?return?adapter(config).then()?...

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

          function?getDefaultAdapter()?{
          ??var?adapter;
          ??if?(typeof?XMLHttpRequest?!==?'undefined')?{
          ????//?For?browsers?use?XHR?adapter
          ????adapter?=?require('./adapters/xhr');
          ??}?else?if?(typeof?process?!==?'undefined'?&&?Object.prototype.toString.call(process)?===?'[object?process]')?{
          ????//?For?node?use?HTTP?adapter
          ????adapter?=?require('./adapters/http');
          ??}
          ??return?adapter;
          }

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

          cancleToken

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

          const?controller?=?new?AbortController();
          const?{?signal?}?=?controller;

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

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

          export?function?dispatchRequest(config)?{
          ??return?new?Promise((resolve,?reject)?=>?{
          ????const?xhr?=?new?XMLHttpRequest()
          ????xhr.open(config.method,?config.url)
          ????xhr.onreadystatechange?=?function?()?{
          ??????if?(xhr.status?>=?200?&&?xhr.status?<=?300?&&?xhr.readyState?===?4)?{
          ????????resolve(xhr.responseText)
          ??????}?else?{
          ????????reject('失敗了')
          ??????}
          ????}

          ????if?(config.cancelToken)?{
          ??????//?Handle?cancellation
          ??????config.cancelToken.promise.then(function?onCanceled(cancel)?{
          ????????if?(!xhr)?{
          ??????????return
          ????????}
          ????????xhr.abort()
          ????????reject(cancel)
          ????????//?Clean?up?request
          ????????xhr?=?null
          ??????})
          ????}
          ????xhr.send()
          ??})
          }

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

          主要是兩種用法:

          使用?cancel token?取消請求

          const?CancelToken?=?axios.CancelToken;
          const?source?=?CancelToken.source();

          axios.get('/user/12345',?{
          ??cancelToken:?source.token
          }).catch(function(thrown)?{
          ??if?(axios.isCancel(thrown))?{
          ????console.log('Request?canceled',?thrown.message);
          ??}?else?{
          ?????//?處理錯誤
          ??}
          });

          axios.post('/user/12345',?{
          ??name:?'new?name'
          },?{
          ??cancelToken:?source.token
          })

          //?取消請求(message?參數(shù)是可選的)
          source.cancel('Operation?canceled?by?the?user.');

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

          const?CancelToken?=?axios.CancelToken;
          let?cancel;

          axios.get('/user/12345',?{
          ??cancelToken:?new?CancelToken(function?executor(c)?{
          ????//?executor?函數(shù)接收一個?cancel?函數(shù)作為參數(shù)
          ????cancel?=?c;
          ??})
          });

          //?cancel?the?request
          cancel();

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

          export?class?cancelToken?{
          ????constructor(exactor)?{
          ????????if?(typeof?executor?!==?'function')?{
          ????????throw?new?TypeError('executor?must?be?a?function.')
          ????????}
          ????????//?這里其實(shí)將promise的控制權(quán)?交給?cancel?函數(shù)
          ????????//?同時做了防止多次重復(fù)cancel?之前?Redux?還有React?源碼中也有類似的案列
          ????????const?resolvePromise;
          ????????this.promise?=??new?Promise(resolve?=>?{
          ????????????resolvePromise?=?resolve;
          ????????})
          ????????this.reason?=?undefined;
          ????????
          ????????const?cancel??=?(message)?=>?{
          ????????????if(this.reason)?{
          ????????????????return;
          ????????????}
          ????????????this.reason?=?'cancel'?+?message;
          ????????????resolvePromise(this.reason);
          ????????}
          ????????exactor(cancel)
          ????}

          ????throwIfRequested()?{
          ????????if(this.reason)?{
          ????????????throw?this.reason
          ????????}
          ????}
          ????
          ????//?source?其實(shí)本質(zhì)上是一個語法糖?里面做了封裝
          ????static?source()?{
          ????????const?cancel;
          ????????const?token?=?new?cancelToken(function?executor(c)?{
          ????????????cancel?=?c;
          ????????});
          ????????return?{
          ????????????token:?token,
          ????????????cancel:?cancel
          ????????};
          ????}

          }

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

          接下來我就測試下我的手寫axios 有沒有什么問題?

          ?

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

          成功了ok, 然后我來測試一下攔截器的功能:代碼更新成下面這樣:

          import?Axios?from?'./axios.js';
          const?config?=?{?url:'http://101.132.113.6:3030/api/mock'?}
          const?axios?=??new?Axios();
          //?在axios?實(shí)例上掛載屬性
          const?err?=?()?=>?{}
          axios.interceptors.request.use((config)=>?{
          ????console.log('我是請求攔截器1')
          ????config.id?=?1;
          ????return??config
          },err?)
          axios.interceptors.request.use((config)=>?{
          ????config.id?=?2
          ????console.log('我是請求攔截器2')
          ????return?config
          },err)
          axios.interceptors.response.use((data)=>?{
          ????console.log('我是響應(yīng)攔截器1',data?)
          ????data?+=?1;
          ????return?data;
          },err)
          axios.interceptors.response.use((data)=>?{
          ????console.log('我是響應(yīng)攔截器2',data?)
          ????return??data
          },err)
          axios.request(config).then(res?=>?{
          ????//?console.log(res,'0000')
          ????//?return?res;
          }).catch(err?=>?{
          ????console.log(err)
          })??console.log(err)})

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

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

          接下來我來是取消請求的兩種方式

          //?第一種方式
          let??cancelFun?=?undefined;
          const?cancelInstance?=?new?cancelToken((c)=>{
          ????cancelFun?=?c;
          });
          config.cancelToken?=?cancelInstance;
          //?50?ms?就取消請求
          setTimeout(()=>{
          ????cancelFun('取消成功')
          },50)

          第二種方式:
          const?{?token,?cancel?}??=?cancelToken.source();
          config.cancelToken?=?token;
          setTimeout(()=>{
          ????cancel()
          },50)

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

          反思

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


          作者:FE_FLY

          juejin.cn/post/6973257605367988260



          往期推薦


          基于WebAssembly的圖片渲染/視頻處理/云原生的場景應(yīng)用有哪些?
          用oclif,碼得更快了,30秒創(chuàng)建腳手架
          專心工作只想搞錢的前端女程序員的2020


          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個專業(yè)的技術(shù)人...

          點(diǎn)個在看支持我吧
          瀏覽 35
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩无码性爱黄色电影播放 | 性无码一区二区三区无码免费 | 欧美一级片在线看 | 艹逼电影| 天天5G天天爽麻豆视频 |