1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        如何防止重復(fù)發(fā)送ajax請求

        共 9257字,需瀏覽 19分鐘

         ·

        2020-09-05 12:01

        作者 | 周浪

        背景

        先來說說重復(fù)發(fā)送ajax請求帶來的問題

        • 場景一:用戶快速點擊按鈕,多次相同的請求打到服務(wù)器,給服務(wù)器造成壓力。如果碰到提交表單操作,而且恰好后端沒有做兼容處理,那么可能會造成數(shù)據(jù)庫中插入兩條及以上的相同數(shù)據(jù)
        • 場景二:用戶頻繁切換下拉篩選條件,第一次篩選數(shù)據(jù)量較多,花費的時間較長,第二次篩選數(shù)據(jù)量較少,請求后發(fā)先至,內(nèi)容先顯示在界面上。但是等到第一次的數(shù)據(jù)回來之后,就會覆蓋掉第二次的顯示的數(shù)據(jù)。篩選結(jié)果和查詢條件不一致,用戶體驗很不好

        常用解決方案

        為了解決上述問題,通常會采用以下幾種解決方案

        • 狀態(tài)變量

          發(fā)送ajax請求前,btnDisable置為true,禁止按鈕點擊,等到ajax請求結(jié)束解除限制,這是我們最常用的一種方案但該方案也存在以下弊端:

          • 與業(yè)務(wù)代碼耦合度高
          • 無法解決上述場景二存在的問題
        • 函數(shù)節(jié)流和函數(shù)防抖

          固定的一段時間內(nèi),只允許執(zhí)行一次函數(shù),如果有重復(fù)的函數(shù)調(diào)用,可以選擇使用函數(shù)節(jié)流忽略后面的函數(shù)調(diào)用,以此來解決場景一存在的問題也可以選擇使用函數(shù)防抖忽略前面的函數(shù)調(diào)用,以此來解決場景二存在的問題該方案能覆蓋場景一和場景二,不過也存在一個大問題:

          • wait time是一個固定時間,而ajax請求的響應(yīng)時間不固定,wait time設(shè)置小于ajax響應(yīng)時間,兩個ajax請求依舊會存在重疊部分,wait time設(shè)置大于ajax響應(yīng)時間,影響用戶體驗??傊褪莣ait time的時間設(shè)定是個難題

        請求攔截和請求取消

        作為一個成熟的ajax應(yīng)用,它應(yīng)該能自己在pending過程中選擇請求攔截和請求取消

        • 請求攔截

          用一個數(shù)組存儲目前處于pending狀態(tài)的請求。發(fā)送請求前先判斷這個api請求之前是否已經(jīng)有還在pending的同類,即是否存在上述數(shù)組中,如果存在,則不發(fā)送請求,不存在就正常發(fā)送并且將該api添加到數(shù)組中。等請求完結(jié)后刪除數(shù)組中的這個api。

        • 請求取消

          用一個數(shù)組存儲目前處于pending狀態(tài)的請求。發(fā)送請求時判斷這個api請求之前是否已經(jīng)有還在pending的同類,即是否存在上述數(shù)組中,如果存在,則找到數(shù)組中pending狀態(tài)的請求并取消,不存在就將該api添加到數(shù)組中。然后發(fā)送請求,等請求完結(jié)后刪除數(shù)組中的這個api

        實現(xiàn)

        接下來介紹一下本文的主角 axioscancel token(查看詳情)。通過axioscancel 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 { // handle error }});
        axios.post('/user/12345', { name: 'new name'}, { cancelToken: source.token})
        // cancel the request (the message parameter is optional)source.cancel('Operation canceled by the user.');

        官網(wǎng)示例中,先定義了一個 const CancelToken = axios.CancelToken,定義可以在axios源碼axios/lib/axios.js目錄下找到

        // Expose Cancel & CancelTokenaxios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');

        示例中調(diào)用了axios.CancelToken的source方法,所以接下來我們再去axios/lib/cancel/CancelToken.js目錄下看看source方法

        /** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */CancelToken.source = function source() {  var cancel;  var token = new CancelToken(function executor(c) {    cancel = c;  });  return {    token: token,    cancel: cancel  };};

        source方法返回一個具有tokencancel屬性的對象,這兩個屬性都和CancelToken構(gòu)造函數(shù)有關(guān)聯(lián),所以接下來我們再看看CancelToken構(gòu)造函數(shù)

        /** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */function CancelToken(executor) {  if (typeof executor !== 'function') {    throw new TypeError('executor must be a function.');  }
        var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; });
        var token = this; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; }
        token.reason = new Cancel(message); resolvePromise(token.reason); });}

        所以souce.token是一個CancelToken的實例,而source.cancel是一個函數(shù),調(diào)用它會在CancelToken的實例上添加一個reason屬性,并且將實例上的promise狀態(tài)resolve掉

        官網(wǎng)另一個示例

        const CancelToken = axios.CancelToken;let cancel;
        axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // An executor function receives a cancel function as a parameter cancel = c; })});
        // cancel the requestcancel();

        它與第一個示例的區(qū)別就在于每個請求都會創(chuàng)建一個CancelToken實例,從而它擁有多個cancel函數(shù)來執(zhí)行取消操作

        我們執(zhí)行axios.get,最后其實是執(zhí)行axios實例上的request方法,方法定義在axios\lib\core\Axios.js

        Axios.prototype.request = function request(config) {  ...  // Hook up interceptors middleware  var chain = [dispatchRequest, undefined];  var promise = Promise.resolve(config);
        this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); });
        this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); });
        while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); }
        return promise;};

        request方法返回一個鏈?zhǔn)秸{(diào)用的promise,等同于

        Promise.resolve(config).then('request攔截器中的resolve方法', 'request攔截器中的rejected方法').then(dispatchRequest, undefined).then('response攔截器中的resolve方法', 'response攔截器中的rejected方法')

        在閱讀源碼的過程中,這些編程小技巧都是非常值得學(xué)習(xí)的

        接下來看看axios\lib\core\dispatchRequest.js中的dispatchRequest方法

        function throwIfCancellationRequested(config) {  if (config.cancelToken) {    config.cancelToken.throwIfRequested();  }}module.exports = function dispatchRequest(config) {  throwIfCancellationRequested(config);  ...  var adapter = config.adapter || defaults.adapter;  return adapter(config).then()};

        如果是cancel方法立即執(zhí)行,創(chuàng)建了CancelToken實例上的reason屬性,那么就會拋出異常,從而被response攔截器中的rejected方法捕獲,并不會發(fā)送請求,這個可以用來做請求攔截

        CancelToken.prototype.throwIfRequested = function throwIfRequested() {  if (this.reason) {    throw this.reason;  }};

        如果cancel方法延遲執(zhí)行,那么我們接著去找axios\lib\defaults.js中的defaults.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;}
        var defaults = { adapter: getDefaultAdapter()}

        終于找到axios\lib\adapters\xhr.js中的xhrAdapter

        module.exports = function xhrAdapter(config) {  return new Promise(function dispatchXhrRequest(resolve, reject) {    ...    var request = new XMLHttpRequest();    if (config.cancelToken) {      // Handle cancellation      config.cancelToken.promise.then(function onCanceled(cancel) {        if (!request) {          return;        }
        request.abort(); reject(cancel); // Clean up request request = null; }); } // Send the request request.send(requestData); })}

        可以看到xhrAdapter創(chuàng)建了XMLHttpRequest對象,發(fā)送ajax請求,在這之后如果執(zhí)行cancel函數(shù)將cancelToken.promise狀態(tài)resolve掉,就會調(diào)用request.abort(),可以用來請求取消

        解耦

        剩下要做的就是將cancelToken從業(yè)務(wù)代碼中剝離出來。我們在項目中,大多都會對axios庫再做一層封裝來處理一些公共邏輯,最常見的就是在response攔截器里統(tǒng)一處理返回code。那么我們當(dāng)然也可以將cancelToken的配置放在request攔截器??蓞⒖糳emo

        let pendingAjax = []const fastClickMsg = '數(shù)據(jù)請求中,請稍后'const CancelToken = axios.CancelTokenconst removePendingAjax = (url, type) => {  const index = pendingAjax.findIndex(i => i.url === url)  if (index > -1) {    type === 'req' && pendingAjax[index].c(fastClickMsg)    pendingAjax.splice(index, 1)  }}
        // Add a request interceptoraxios.interceptors.request.use( function (config) { // Do something before request is sent const url = config.url removePendingAjax(url, 'req') config.cancelToken = new CancelToken(c => { pendingAjax.push({ url, c }) }) return config }, function (error) { // Do something with request error return Promise.reject(error) })
        // Add a response interceptoraxios.interceptors.response.use( function (response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data removePendingAjax(response.config.url, 'resp') return new Promise((resolve, reject) => { if (+response.data.code !== 0) { reject(new Error('network error:' + response.data.msg)) } else { resolve(response) } }) }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error Message.error(error) return Promise.reject(error) })

        每次執(zhí)行request攔截器,判斷pendingAjax數(shù)組中是否還存在同樣的url。如果存在,則刪除數(shù)組中的這個api并且執(zhí)行數(shù)組中在pending的ajax請求的cancel函數(shù)進行請求取消,然后就正常發(fā)送第二次的ajax請求并且將該api添加到數(shù)組中。等請求完結(jié)后刪除數(shù)組中的這個api

        let pendingAjax = []const fastClickMsg = '數(shù)據(jù)請求中,請稍后'const CancelToken = axios.CancelTokenconst removePendingAjax = (config, c) => {  const url = config.url  const index = pendingAjax.findIndex(i => i === url)  if (index > -1) {    c ? c(fastClickMsg) : pendingAjax.splice(index, 1)  } else {    c && pendingAjax.push(url)  }}
        // Add a request interceptoraxios.interceptors.request.use( function (config) { // Do something before request is sent config.cancelToken = new CancelToken(c => { removePendingAjax(config, c) }) return config }, function (error) { // Do something with request error return Promise.reject(error) })
        // Add a response interceptoraxios.interceptors.response.use( function (response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data removePendingAjax(response.config) return new Promise((resolve, reject) => { if (+response.data.code !== 0) { reject(new Error('network error:' + response.data.msg)) } else { resolve(response) } }) }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error Message.error(error) return Promise.reject(error) })

        每次執(zhí)行request攔截器,判斷pendingAjax數(shù)組中是否還存在同樣的url。如果存在,則執(zhí)行自身的cancel函數(shù)進行請求攔截,不重復(fù)發(fā)送請求,不存在就正常發(fā)送并且將該api添加到數(shù)組中。等請求完結(jié)后刪除數(shù)組中的這個api

        總結(jié)

        axios 是基于 XMLHttpRequest 的封裝,針對 fetch ,也有類似的解決方案 AbortSignal 查看詳情。大家可以針對各自的項目進行選取

        ??愛心三連擊

        1.看到這里了就點個在看支持下吧,你的在看是我創(chuàng)作的動力。

        2.關(guān)注公眾號程序員成長指北,回復(fù)「1」加入Node進階交流群!「在這里有好多 Node 開發(fā)者,會討論 Node 知識,互相學(xué)習(xí)」!

        3.也可添加微信【ikoala520】,一起成長。


        “在看轉(zhuǎn)發(fā)”是最大的支持

        瀏覽 49
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            人妻黄色视频 | 乳女教师欲乱动漫无修版在线观看 | 无码毛片一区二区三区视频免费播 | 国产A级毛片又黄又暴无码 | 久久香视频 | 青青草com| 99久久免费看国产精品 | 操小嫩逼 | 温碧霞三级全做爰电影 | 999精品视频 |