【问题标题】:How to avoid sending multiple duplicate AJAX requests in axios如何避免在 axios 中发送多个重复的 AJAX 请求
【发布时间】:2019-09-16 00:15:32
【问题描述】:

是否可以使用 axios 自动限制所有发往特定端点列表的请求?也许使用 axios 拦截器?

目前我限制了发送 axios 请求的用户操作,但问题是我必须在任何有导致某些 AJAX 请求的用户操作的地方写这个。像这样

  const throttledDismissNotification = throttle(dismissNotification, 1000)

  const dismiss = (event: any) => {
    throttledDismissNotification();
  };

  render() {
    return (
      <Button onClick={dismiss}>Dismiss Notification</Button>
    )
  }

这会导致很多混乱,我想知道这是否可以自动化。

类似:

if(request.url in listOfEndpointsToThrottle && request.params in cacheOfPreviousRequestsToThisEndpoint) {
  StopRequest();
}

显然这是伪代码,但你明白了。

【问题讨论】:

  • 第一步可能是在你的 Redux 存储中添加一个标志,如isFetchingisCreatingisUpdating 等,并在该标志已经存在时禁用进行调用的按钮true.
  • @GG。我已经实现了类似的东西..a loading 状态在您发送请求时设置为 true,在返回时设置为 false。但是,与上面的解决方案类似,这会使代码库变得混乱且乏味。
  • @ManavM 我有一个与您的问题相关的 SO 讨论 stackoverflow.com/questions/55919714/… 看看是否对您有帮助。
  • 限制 axios 请求调用非常容易。真正令人头疼的是如何处理从那些无效请求返回的承诺,我们应该如何定义它们的行为?他们会永远等待吗?您的代码的其他部分是否期待或准备好处理未决的承诺?
  • @Qiulang 只需检查您的链接。 Bergi's answer 不错。这个问题很难一概而论,而且我认为没有完美的万能解决方案来限制/消除任何返回承诺的函数。

标签: javascript reactjs axios redux-thunk


【解决方案1】:

也许你可以尝试使用 axios 提供的Cancellation 功能。

有了它,您可以确保没有任何两个(或更多,取决于您的实现)处于待处理状态的类似请求。

下面,您将找到一个简单的小示例,说明如何确保只处理最新的请求。你可以稍微调整一下,让它像一个请求池一样工作

    import axios, { CancelToken } from 'axios';

    const pendingRequests = {};

    const makeCancellable = (headers, requestId) => {
      if (!requestId) {
        return headers;
      }

      if (pendingRequests[requestId]) {
        // cancel an existing request
        pendingRequests[requestId].cancel();
      }
      const source = CancelToken.source();
      const newHeaders = {
        ...headers,
        cancelToken: source.token
      };
      pendingRequests[requestId] = source;
      return newHeaders;
    };

    const request = ({
      url,
      method = 'GET',
      headers,
      id
    }) => {
      const requestConfig = {
        url,
        method,
        headers: makeCancellable(headers || {}, id)
      };

      return axios.request(requestConfig)
        .then((res) => {
          delete pendingRequests[id];
          return ({ data: res.data });
        })
        .catch((error) => {
          delete pendingRequests[id];
          if (axios.isCancel(error)) {
             console.log(`A request to url ${url} was cancelled`); // cancelled
          } else {
             return handleReject(error);
          }
        });
    };

    export default request;

【讨论】:

  • 我不认为取消之前的请求是最好的解决方案。 a) 它抛出错误,这是用户需要处理的开销。 b) 请求仍然被触发,只是稍后被取消。
【解决方案2】:

限制 axios 请求本身非常容易。真正令人头疼的是如何处理从无效请求返回的承诺。在处理从无效 axios 请求返回的承诺时,什么被认为是正常行为?他们应该永远等待吗?

我没有看到任何完美的解决方案来解决这个问题。但后来我找到了一个有点作弊的解决方案:

如果我们不限制 axios 调用,而是限制实际的 XMLHttpRequest 会怎样?

这使事情变得更容易,因为它避免了承诺问题,并且更容易实现。这个想法是为最近的请求实现一个缓存,如果一个新的请求匹配一个最近的请求,你只需从缓存中提取结果并跳过 XMLHttpRequest。

由于axios interceptors work的方式,下面的sn-p可以有条件地跳过某个XHR调用:

// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
  /* check the cache, if hit, then intentionally throw
   * this will cause the XHR call to be skipped
   * but the error is still handled by response interceptor
   * we can then recover from error to the cached response
   **/ 
  if (requestCache.isCached(config)) {
    const skipXHRError = new Error('skip')
    skipXHRError.isSkipXHR = true
    skipXHRError.request = config
    throw skipXHRError
  } else {
    /* if not cached yet
     * check if request should be throttled
     * then open up the cache to wait for a response
     **/
    if (requestCache.shouldThrottle(config)) {
      requestCache.waitForResponse(config)
    }
    return config;
  }
});

// This should be the *first* response interceptor to add
axios.interceptors.response.use(function (response) {
  requestCache.setCachedResponse(response.config, response)
  return response;
}, function (error) {
  /* recover from error back to normality
   * but this time we use an cached response result
   **/
  if (error.isSkipXHR) {
    return requestCache.getCachedResponse(error.request)
  }
  return Promise.reject(error);
});

【讨论】:

  • 你的例子有助于展示拦截器是如何工作的(我自己没有弄清楚)但是我会说返回缓存的承诺似乎更容易。
  • @Qiulang 你是对的。我试图做的基本上是缓存请求后返回的第一个承诺。只是我以 axios 特定的方式来做。 bergi 对您的问题的回答显示了如何编写通用实用程序,但您仍然需要决定何时使用或不使用此实用程序。我的展示了适合 OP 案例的缓存策略的基本思想。
  • 但老实说,我一开始并没有意识到这个 is 返回缓存的承诺。编辑了答案以删除该误导行。
  • 我喜欢这个解决方案...破解拦截器以确保可以忽略匹配条件的请求。正是我要找的……谢谢。
  • 我确实想提一下,可能有比此处的 skipXHRError hack 更简单的方法来停止请求:github.com/axios/axios/issues/1497#issuecomment-404211504
【解决方案3】:

我有一个类似的问题,通过我的研究,它似乎缺乏一个好的解决方案。我看到的只是一些临时解决方案,所以我为 axios 打开一个问题,希望有人能回答我的问题https://github.com/axios/axios/issues/2118

我也找到了这篇文章Throttling Axios requests,但我没有尝试他建议的解决方案。

我有一个与此相关的讨论My implementation of debounce axios request left the promise in pending state forever, is there a better way?

【讨论】:

    【解决方案4】:

    我写完了一个,@hackape 谢谢你的回答,代码如下:

    const pendings = {}
    const caches = {}
    const cacheUtils = {
       getUniqueUrl: function (config) {
    
         // you can set the rule based on your own requirement
         return config.url + '&' + config.method
       },
       isCached: function (config) {
         let uniqueUrl = this.getUniqueUrl(config)
         return caches[uniqueUrl] !== undefined
       },
       isPending: function (config) {
         let uniqueUrl = this.getUniqueUrl(config)
         if (!pendings[uniqueUrl]) {
           pendings[uniqueUrl] = [config]
           return false
         } else {
           console.log(`cache url: ${uniqueUrl}`)
           pendings[uniqueUrl].push(config)
           return true
         }
       },
       setCachedResponse: function (config, response) {
         let uniqueUrl = this.getUniqueUrl(config)
         caches[uniqueUrl] = response
         if (pendings[uniqueUrl]) {
           pendings[uniqueUrl].forEach(configItem => {
             configItem.isFinished = true
           })
         }
       },
       getError: function(config) {
         const skipXHRError = new Error('skip')
         skipXHRError.isSkipXHR = true
         skipXHRError.requestConfig = config
         return skipXHRError
       },
       getCachedResponse: function (config) {
         let uniqueUrl = this.getUniqueUrl(config)
         return caches[uniqueUrl]
       }
     }
     // This should be the *last* request interceptor to add
     axios.interceptors.request.use(function (config) {
    
        // to avoid careless bug, only the request that explicitly declares *canCache* parameter can use cache
       if (config.canCache) {
    
         if (cacheUtils.isCached(config)) {
           let error = cacheUtils.getError(config)
           throw error
         }
         if (cacheUtils.isPending(config)) {
           return new Promise((resolve, reject) => {
             let interval = setInterval(() => {
               if(config.isFinished) {
                 clearInterval(interval)
                 let error = cacheUtils.getError(config)
                 reject(error)
               }
             }, 200)
           });
         } else {
    
           // the head of cacheable requests queue, get the response by http request 
           return config
         }
       } else {
         return config
       }
     });
    

    【讨论】:

      猜你喜欢
      • 2014-02-03
      • 1970-01-01
      • 2020-12-15
      • 1970-01-01
      • 2023-03-15
      • 2019-10-15
      • 2017-08-22
      • 2017-06-17
      • 1970-01-01
      相关资源
      最近更新 更多