從axios源碼角度解決bug的過(guò)程記錄
現(xiàn)象
公司的一個(gè) H5 站點(diǎn)在頭條 App 里白屏,在手百、QQ 瀏覽器、Safari、Chrome 等都正常
排查思路
1. 引入 vConsole 在移動(dòng)端調(diào)試
因?yàn)橐苿?dòng)端沒(méi)有 PC 里那樣方便的調(diào)試工具可以清晰的查看 log 和 network 之類(lèi)有用的信息,只能借助 vConsole
、eruda
這類(lèi)移動(dòng)端調(diào)試工具,可以在移動(dòng)端實(shí)現(xiàn)類(lèi)似 PC 瀏覽器里的調(diào)試功能
2. 從大范圍到小范圍的 log
因?yàn)橐苿?dòng)端無(wú)法 debugger,只能逐步的 log 去定位問(wèn)題。我們采用的是 vue 技術(shù)棧,可以從 main.js 到路由組件的生命周期里去添加 log,通過(guò)這種方式定位到了在一個(gè)接口請(qǐng)求的方法之后的代碼都不會(huì)請(qǐng)求,奇怪的是也沒(méi)有拋出任何異常,并且在 vConsole
里的 network 下找到了該請(qǐng)求也是正常的響應(yīng),如下代碼:
try { console.log('start fetch') const res = await FetchXXX(); console.log(res) } catch (e) { console.log(e) }
只打印出了start fetch
,請(qǐng)求的結(jié)果和捕獲的異常里都沒(méi)有打印出東西,至此又縮小了排查的范圍,一定是請(qǐng)求的響應(yīng)處理部分出現(xiàn)問(wèn)題了。
因?yàn)槲覀兊恼?qǐng)求接口方法是基于 axios 統(tǒng)一封裝的,本以為是封裝的哪個(gè)環(huán)節(jié)有不兼容頭條的代碼,通過(guò)不斷的 log,發(fā)現(xiàn) request interceptor
都執(zhí)行了,但是 response interctpor
一個(gè)都沒(méi)執(zhí)行,好家伙,看著不像我們封裝的問(wèn)題,應(yīng)該是 axios
內(nèi)部處理的問(wèn)題。
3. axios 源碼一覽
通過(guò)上面的一頓操作,基本可以確認(rèn)是 axios
內(nèi)部處理響應(yīng)數(shù)據(jù)時(shí)可能有部分兼容性問(wèn)題,去 github 上去找 issue 也沒(méi)找到相關(guān)的問(wèn)題。 沒(méi)辦法只能去看 axios
的源碼了,我們前面定位到是請(qǐng)求發(fā)送沒(méi)問(wèn)題,在響應(yīng)的時(shí)候應(yīng)該是遇到了啥異常,并且還沒(méi)有拋出。
項(xiàng)目里 axios
用的版本是 0.24.0
,我直接在 node_modules
里看 axios
的源碼,因?yàn)?npm 下載的 axios
包沒(méi)有用 webpack
或者 rollup
之類(lèi)的編譯過(guò),所以在 node_modules
里看和看源碼無(wú)異,并且更可靠(因?yàn)轫?xiàng)目里是直接引用的這個(gè)代碼)。打開(kāi) axios
源碼目錄
axios
的源碼不算復(fù)雜,目錄光看命名基本也能猜到是干啥的,幾個(gè)主要目錄如下:
adapters
:針對(duì)不同的宿主環(huán)境使用不同的請(qǐng)求 api,目前只有瀏覽器端和 nodejs 端,adapters
目錄下的 xhr.js
和 http.js
分別對(duì)這兩種環(huán)境進(jìn)行了實(shí)現(xiàn)。
cancel
:取消請(qǐng)求的相關(guān)源碼
core
:axios
的核心源碼
helpers
:工具方法
排查角度 - interceptor
之前定位到所有的 response interceptor
都沒(méi)執(zhí)行,根據(jù)這個(gè)現(xiàn)象我先找到 interceptor
相關(guān)的代碼,在 core/Axios.js
里找到以下代碼:
// 請(qǐng)求攔截器列表 var requestInterceptorChain = []; var synchronousRequestInterceptors = true; // 遍歷所有的請(qǐng)求攔截器并放入到列表中 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; // 請(qǐng)求攔截器后進(jìn)先出(棧) requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); // 響應(yīng)攔截器列表 var responseInterceptorChain = []; // 遍歷所有的響應(yīng)攔截器并放入到列表中 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { // 請(qǐng)求攔截器先進(jìn)先出(隊(duì)列) responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); var promise; if (!synchronousRequestInterceptors) { // axios執(zhí)行隊(duì)列,包含所有的請(qǐng)求攔截器、請(qǐng)求方法、響應(yīng)攔截器,并按順序排列 // 這里的dispatchRequest就是實(shí)際的請(qǐng)求方法 var chain = [dispatchRequest, undefined]; // 將所有的請(qǐng)求攔截器放請(qǐng)求的前面 Array.prototype.unshift.apply(chain, requestInterceptorChain); // 將所有的響應(yīng)攔截器放請(qǐng)求的后面 chain = chain.concat(responseInterceptorChain); promise = Promise.resolve(config); // 依次執(zhí)行整個(gè)執(zhí)行隊(duì)列,直至隊(duì)列為空 while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }
以上代碼就是 axios
對(duì) interceptor
核心的處理,一開(kāi)始懷疑是不是所有的響應(yīng)攔截器沒(méi)有被加入到執(zhí)行隊(duì)列中,log 后發(fā)現(xiàn)沒(méi)有問(wèn)題,所有響應(yīng)攔截器都在隊(duì)列中。
排查角度 - xhr
上面排除了 interceptor
的問(wèn)題,我又懷疑 axios
封裝的 xhr
在響應(yīng)的時(shí)候有啥兼容性問(wèn)題,于是查看 adapters/xhr.js
,并找到處理響應(yīng)相關(guān)的代碼:
// 省略了許多不必要的代碼 var request = new XMLHttpRequest(); function onloadend() { console.log('onloadend') // ... } if ('onloadend' in request) { console.log('use onloadend') request.onloadend = onloadend; } else { console.log('use onreadystatechange') request.onreadystatechange = function handleLoad() { if (!request || request.readyState !== 4) { return; } setTimeout(onloadend); }; }
axios
處理響應(yīng)的代碼大致如上,如果瀏覽器 xhr
對(duì)象包含 onloadend
事件就監(jiān)聽(tīng) onloadend
事件,否則監(jiān)聽(tīng) onreadystatechange
來(lái)實(shí)現(xiàn)請(qǐng)求響應(yīng)的回調(diào),通過(guò) log 我發(fā)現(xiàn)打印了use onreadystatechange
,但是沒(méi)打印onloadend
。
至此終于找到問(wèn)題所在,頭條 iOS 該版本的 xhr 對(duì)象雖然聲明了 onloadend 事件但是請(qǐng)求結(jié)束后并未回調(diào)該事件!
4. 解決問(wèn)題
因?yàn)槲覀兤渌军c(diǎn)在頭條上是可以正常訪(fǎng)問(wèn)的,我看了那些站點(diǎn)的代碼,發(fā)現(xiàn)他們用的 axios
版本是 0.18.1
,看下 0.18.1
版本的 adapters/xhr.js
文件對(duì)于響應(yīng)的處理:
他是直接使用的 onreadystatechange
方法來(lái)監(jiān)聽(tīng)的,所以沒(méi)有問(wèn)題。
至此整個(gè)排查結(jié)束,最后通過(guò)降級(jí) axios 版本解決該問(wèn)題。
以上就是從axios源碼角度解決bug的過(guò)程記錄的詳細(xì)內(nèi)容,更多關(guān)于axios源碼角度bug解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript-表格排序(降序/反序)實(shí)現(xiàn)介紹(附圖)
使用了Array方法、sort:降序、reverse:反序完成了基本功能,對(duì)于聯(lián)合排序沒(méi)有實(shí)現(xiàn),感興趣的朋友可以參考下哈2013-05-05實(shí)現(xiàn)div滾動(dòng)條默認(rèn)最底部以及默認(rèn)最右邊的示例代碼
下面小編就為大家分享一篇實(shí)現(xiàn)div滾動(dòng)條默認(rèn)最底部以及默認(rèn)最右邊的示例代碼,代碼非常簡(jiǎn)潔,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11淺析IE10兼容性問(wèn)題(frameset的cols屬性)
主頁(yè)用frameset嵌了兩個(gè)頁(yè)面,左側(cè)為菜單欄,可以通過(guò)改變 frameset的cols來(lái)收縮。別的瀏覽器正常,但I(xiàn)E10卻沒(méi)任何的反應(yīng)2014-01-01微信小程序授權(quán)登錄及解密unionId出錯(cuò)的方法
這篇文章主要介紹了微信小程序授權(quán)登錄及解密unionId出錯(cuò)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09前端開(kāi)發(fā)基礎(chǔ)javaScript的六大作用
這篇文章主要介紹了前端開(kāi)發(fā)基礎(chǔ)javaScript的六大作用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08JS實(shí)現(xiàn)進(jìn)入頁(yè)面時(shí)漸變背景色的方法
這篇文章主要介紹了JS實(shí)現(xiàn)進(jìn)入頁(yè)面時(shí)漸變背景色的方法,涉及javascript操作css控制背景色漸變的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02