封裝 axios 取消重復(fù)請(qǐng)求
編者按:本文作者舒麗琦,奇舞團(tuán)前端開發(fā)工程師
在我們web開發(fā)過(guò)程中,很多地方需要我們?nèi)∠貜?fù)的請(qǐng)求。但是哪種場(chǎng)合需要我們?nèi)∠兀课覀內(nèi)绾稳∠兀繋е@些問(wèn)題我們閱讀本文。
閱讀完本文,你將了解以下內(nèi)容:
需要取消重復(fù)請(qǐng)求的場(chǎng)景
我們?nèi)绾稳∠貜?fù)請(qǐng)求
axios如何取消重復(fù)的請(qǐng)求
封裝axios
如何給開源的項(xiàng)目提供源碼
如何在本地調(diào)試npm包
提出問(wèn)題
最近做的項(xiàng)目中,用的用戶經(jīng)常遇到這樣的問(wèn)題:
用戶頻繁切換篩選條件去請(qǐng)求數(shù)據(jù),初次的篩選條件數(shù)據(jù)量大。用的時(shí)間比較多。后面的篩選條件的數(shù)據(jù)量小。導(dǎo)致后面請(qǐng)求的數(shù)據(jù)先返回。內(nèi)容先顯示在頁(yè)面上。但是等一段時(shí)間,初次(或者前面)的請(qǐng)求數(shù)據(jù)返回了, 會(huì)覆蓋后面的請(qǐng)求的數(shù)據(jù)。這就導(dǎo)致了篩選條件和內(nèi)容不一致的情況。
用戶點(diǎn)擊了一次提交按鈕,接口沒(méi)有很快響應(yīng),導(dǎo)致頁(yè)面沒(méi)辦法做邏輯語(yǔ)句判斷的提示。用戶覺(jué)得可能沒(méi)提交上,便會(huì)快速又點(diǎn)了按鈕幾次。如果后端沒(méi)有去重的判斷,就會(huì)導(dǎo)致數(shù)據(jù)中有很多條重復(fù)的數(shù)據(jù)。
這些問(wèn)題給用戶的體驗(yàn)是很不友好的。那么取消無(wú)用的請(qǐng)求是很有必要的。
解決思路
我們用的請(qǐng)求庫(kù)是axios。那么我們可以在請(qǐng)求的時(shí)候攔截請(qǐng)求判斷當(dāng)前的請(qǐng)求是否重復(fù),如果重復(fù)我們就取消當(dāng)前的請(qǐng)求。大致的實(shí)現(xiàn)過(guò)程如下:
我們把目前處于pending的請(qǐng)求存儲(chǔ)(假如我們放在一個(gè)數(shù)組)起來(lái)。每個(gè)請(qǐng)求發(fā)送之前我們都要判斷當(dāng)前這個(gè)請(qǐng)求是否已經(jīng)存在于這個(gè)數(shù)組。如果存在,說(shuō)明請(qǐng)求重復(fù)了,我們就在數(shù)組中找到重復(fù)的請(qǐng)求并且取消。如果不存在,說(shuō)明這個(gè)請(qǐng)求不是重復(fù)的,正常發(fā)送并且把這個(gè)請(qǐng)求api添加在數(shù)據(jù)中,等請(qǐng)求結(jié)束之后刪除數(shù)組中的這個(gè)api。
我們這個(gè)解決思路有了,但是axios如何取消請(qǐng)求的呢?我們先來(lái)了解下
axios 如何取消請(qǐng)求
查看axios文檔發(fā)現(xiàn)axios提供了兩種取消請(qǐng)求的方法(http://www.axios-js.com/zh-cn/docs/#%E5%8F%96%E6%B6%88)
第一種方法
通過(guò)axios.CancelToken.source生成取消令牌token和取消方法cancel
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?{
????// 處理錯(cuò)誤
??}
});
axios.post('/user/12345',?{
??name:?'new name'
},?{
??cancelToken:?source.token
})
// 取消請(qǐng)求 (消息參數(shù)是可選的)
source.cancel('Operation canceled by the user.');
第二種方式
通過(guò)傳遞一個(gè) executor 函數(shù)到 CancelToken 的構(gòu)造函數(shù)來(lái)創(chuàng)建 cancel token
const?CancelToken?=?axios.CancelToken;
let?cancel;
axios.get('/user/12345',?{
??cancelToken:?new?CancelToken(function?executor(c)?{
????// executor 函數(shù)接收一個(gè) cancel 函數(shù)作為參數(shù)
????cancel?=?c;
??})
});
// 取消請(qǐng)求
cancel();
封裝axios
解決取消請(qǐng)求的思路有了,取消請(qǐng)求的辦法也有了,那么剩下的就是封裝了
由于同事之前已經(jīng)封裝了axios——very-axios(https://github.com/verymuch/very-axios) (基于 axios 進(jìn)行二次封裝,更簡(jiǎn)單、更統(tǒng)一地使用 axios)。那么我們就這個(gè)基礎(chǔ)上提一個(gè)pr吧。那么從現(xiàn)在開始我們就一步一步的來(lái)實(shí)現(xiàn),這個(gè)過(guò)程包含了【如何給開源的項(xiàng)目貢獻(xiàn)代碼】【如何在本地調(diào)試npm】如果已經(jīng)了解的同學(xué)可以直接略過(guò)。
準(zhǔn)備工作
由于同事已經(jīng)封裝了axios并且已經(jīng)開源了。那么我貢獻(xiàn)代碼的方式主要有兩種:
代碼倉(cāng)庫(kù)的管理者給我們添加這個(gè)倉(cāng)庫(kù)的寫入權(quán)限,如果這樣,我們就可以直接提push。
如果我們沒(méi)有權(quán)限(大多數(shù)情況)。我們使用經(jīng)典的fork & pull request 的方式來(lái)提交代碼。
我們采用的第二種方式。我們?nèi)?very-axios(https://github.com/verymuch/very-axios) 把代碼fork到自己的倉(cāng)庫(kù)(如果你還沒(méi)有自己的github,需要自己注冊(cè)下哦)。

那么你回到自己的github倉(cāng)庫(kù)下面就會(huì)看有一個(gè)一摸一樣的項(xiàng)目

那么我們現(xiàn)在就可以git clone這個(gè)倉(cāng)庫(kù)的代碼到本地,新建branch進(jìn)行開發(fā),就比如我新建了一個(gè)這樣的branch:
現(xiàn)在已經(jīng)有本地的代碼了,但是我們?nèi)绾伪镜鼗{(diào)試npm包呢?那就需要npm link 了
首先在我們要修改的npm 包中npm link:

之后我們會(huì)得到
/Users/shuliqi/.nvm/versions/node/v12.17.0/lib/node_modules/very-axios?->?/Users/shuliqi/study/axios/very-axios
這意思就是我們把very-axios鏈接到全局的node_modules
然后我們進(jìn)入我們my-project-of-axios 目錄下面執(zhí)行npm link very-axios 如圖:

這意思就是very-axios被安裝在my-project-of-axios 下面了。very-axios的修改都會(huì)同步到my-project-of-axios。就實(shí)現(xiàn)本地測(cè)試了。
我們?cè)?span style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;border-width: 1px;border-style: solid;border-color: rgb(225, 225, 232);font-size: 12px;font-family: monospace;color: rgb(221, 17, 68);background-color: rgb(247, 247, 249);border-radius: 2px;word-break: break-all;">my-project-of-axios中的HelloWorld.vue文件中做列子。
如果這里看的不是很懂的同學(xué)可以看看這兩篇文章:如何在本地調(diào)試npm包(https://github.com/allenGKC/Blog/issues/13)。如何使用 GitHub Flow 給開源項(xiàng)目貢獻(xiàn)代碼(https://juejin.im/post/6844903636863041550)
開始封裝
準(zhǔn)備工作完成了, 那我們開始封裝的事情。根據(jù)我們之前的思路。我們采用axios 如何取消請(qǐng)求的第一種方式。
聲明一個(gè)Map。用來(lái)存儲(chǔ)每個(gè)請(qǐng)求的 標(biāo)識(shí) 和 取消的函數(shù)
// 存儲(chǔ)每個(gè)請(qǐng)求的標(biāo)識(shí)和取消的函數(shù)
this.pendingAjax?=?new?Map();
自定一個(gè)字段來(lái)讓用戶自己決定是否需要取消重復(fù)的請(qǐng)求
// 是否取消重復(fù)的請(qǐng)求
cancelDuplicated?=?false,
自定一個(gè)字段來(lái)讓用戶是否有全局的統(tǒng)一的設(shè)置重復(fù)標(biāo)識(shí)的函數(shù)。如果沒(méi)有設(shè)置全局的統(tǒng)一的函數(shù),則默認(rèn)是請(qǐng)求的method 和url作為重復(fù)的標(biāo)識(shí)
// 生成重復(fù)標(biāo)識(shí)的方式
duplicatedKeyFn,
this.duplicatedKeyFn?=?isFunction(duplicatedKeyFn)???duplicatedKeyFn?:?(config)?=>?`${config.method}${config.url}`;
添加請(qǐng)求
/**
?* 將請(qǐng)求添加到pendingAjax
?* @param {Object} config
?*/
addPendingAjax(config)?{
??//?是否需要取消重復(fù)的請(qǐng)求
??if?(!this.cancelDuplicated)?return
??const?veryConfig?=?config.veryConfig?||?{};
??const?duplicatedKey?=?JSON.stringify({
????duplicatedKey:?veryConfig.duplicatedKey?||?this.duplicatedKeyFn(config),
????type:?REQUEST_TYPE.DUPLICATED_REQUEST,
??});
??config.cancelToken?=?config.cancelToken?||?new?axios.CancelToken((cancel)?=>?{
????// 如果pendingAjax中不存在當(dāng)前請(qǐng)求,添加進(jìn)去
????if?(duplicatedKey?&&?!this.pendingAjax.has(duplicatedKey))?{
??????this.pendingAjax.set(duplicatedKey,?cancel);
????}
??});
}
這里面我們可以使用duplicatedKey字段來(lái)讓用戶對(duì)單一請(qǐng)求自定義重復(fù)的標(biāo)識(shí)。或者可以使用一個(gè)函數(shù)duplicatedKeyFn統(tǒng)一的讓用戶自定義重復(fù)的標(biāo)識(shí)
刪除請(qǐng)求
/**
???* 從pendingAjax中刪除請(qǐng)求
???* @param {Object} config
???*/
??removePendingAjax(config)?{
????//?是否需要取消重復(fù)的請(qǐng)求
????if?(!this.cancelDuplicated)?return
????const?veryConfig?=?config.veryConfig?||?{};
????const?duplicatedKey?=?JSON.stringify({
??????duplicatedKey:?veryConfig.duplicatedKey?||?this.duplicatedKeyFn(config),
??????type:?REQUEST_TYPE.DUPLICATED_REQUEST,
????});
????// 如果pendingAjax中存在當(dāng)前請(qǐng)求, 取消當(dāng)前請(qǐng)求并將其刪除
????if?(duplicatedKey?&&?this.pendingAjax.has(duplicatedKey))?{
??????const?cancel?=?this.pendingAjax.get(duplicatedKey);
??????cancel(duplicatedKey);
??????this.pendingAjax.delete(duplicatedKey);
????}
??}
封裝好了, 我們?cè)谀睦锸褂媚兀靠隙ㄊ窃谡?qǐng)求開始之前和請(qǐng)求完成之后使用。
在請(qǐng)求之前
// 攔截請(qǐng)求
this.axios.interceptors.request.use((config)?=>?{
??//?在請(qǐng)求開始之前檢查先前的請(qǐng)求
??this.removePendingAjax(config);
??// 將當(dāng)前請(qǐng)求添加到pendingAjax
??this.addPendingAjax(config);
??// ...
});
在請(qǐng)求完成之后去掉該請(qǐng)求
// 攔截響應(yīng)
this.axios.interceptors.response.use(response?=>?{
??removePending(response)
??return?response
},?error?=>?{
??// ...
})
到現(xiàn)在已經(jīng)完成了該有的功能, 但是取消請(qǐng)求的錯(cuò)誤我們不該返回給用戶。所以:
(err)?=>?{
//?類型是否為重復(fù)請(qǐng)求
let?isDuplicatedType;
try?{
??const?errorType?=?(JSON.parse(error.message)?||?{}).type
??isDuplicatedType?=?errorType?===?REQUEST_TYPE.DUPLICATED_REQUEST;
}?catch?(error)?{
??isDuplicatedType?=?false
}
if?(isDuplicatedType)?return;
}
我們?cè)谡?qǐng)求完成之后的err里面做一個(gè)判斷,判斷如果當(dāng)前請(qǐng)求是取消的類型,我們就不返回給用戶錯(cuò)誤的提示信息。
總結(jié)
至此,完成了我們的封裝。完成的pr地址:(https://github.com/verymuch/very-axios/pull/1)。本文測(cè)試npm包的項(xiàng)目地址:(https://github.com/shuliqi/my-project-of-axios)
??愛心三連擊 1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的「點(diǎn)贊,在看」是我創(chuàng)作的動(dòng)力。
2.關(guān)注公眾號(hào)
程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端?開發(fā)者,會(huì)討論?前端 Node 知識(shí),互相學(xué)習(xí)」!3.也可添加微信【ikoala520】,一起成長(zhǎng)。
“在看轉(zhuǎn)發(fā)”是最大的支持

