5
votes

Comment éviter d'envoyer plusieurs requêtes AJAX en double dans axios

Est-il possible de limiter automatiquement toutes les demandes allant à une liste particulière de points de terminaison à l'aide d'axios? Peut-être utiliser l'intercepteur axios?

Actuellement, j'arrête l'action utilisateur qui envoie la requête axios, mais le problème avec cela est que je dois écrire ceci partout où j'ai une action utilisateur qui entraîne une requête AJAX. Comme ça

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

Cela entraîne beaucoup de désordre et je me demandais si cela pouvait être automatisé.

Quelque chose comme:

  const throttledDismissNotification = throttle(dismissNotification, 1000)

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

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

Évidemment, c'est un pseudocode mais vous voyez l'idée.


8 commentaires

La première étape est probablement d'ajouter un drapeau dans votre boutique Redux, comme isFetching , isCreating , isUpdating , etc…, et de désactiver le bouton effectuant l'appel lorsque ce drapeau est déjà true .


@GG. J'ai quelque chose comme celui-ci déjà implémenté..un état de loading qui est mis à vrai lorsque vous envoyez une demande et de retour à faux quand il revient. Cependant, similaire à la solution ci-dessus, cela encombre la base de code et est fastidieux.


@ManavM J'ai une discussion SO liée à votre question stackoverflow.com/questions/55919714/… voir si cela vous aide.


Il est assez facile d'étrangler un appel de demande axios. Le vrai casse-tête est de savoir comment gérer les promesses qui sont renvoyées par ces demandes annulées, comment devrions-nous définir leur comportement? Restent-ils en suspens pour toujours? D'autres parties de votre code attendent-elles ou sont-elles prêtes à gérer des promesses toujours en suspens?


@Qiulang vérifiez simplement votre lien. La réponse de Bergi est bonne. Le problème est difficile à généraliser, et je ne vois pas qu'il existe une solution unique parfaite pour accélérer / supprimer toute fonction de retour de promesse.


La promesse en suspens @hackape ne sera pas du tout gérée par la chaîne de promesse, mais collectée par les ordures. Voilà l'essentiel. Voir mon autre discussion avec Bergi stackoverflow.com/questions/55861970/...


@Qiulang Je ne suis pas préoccupé par le fait que les promesses toujours en suspens aient de mauvais effets secondaires, ce n'est pas le cas. Je crains qu'ils n'aient aucun effet. Lorsque nous .then sur un Axios demandons généralement nous supposons qu'il soit suceed ou échouent, il est rare que l' on considère le cas toujours en attente. Il introduira une surcharge à l'ensemble de la base de code pour prendre en compte un troisième état à gérer.


@hackape, d'accord, c'est pourquoi j'étais confus au début pour mon autre question. Alors maintenant, j'ai utilisé la suggestion de Bergi const never = new Promise (résoudre => {/ * ne rien faire * /}) et ajouter un commentaire à ce qui ne signifie jamais dans mon code.


5 Réponses :


8
votes

Vous pourriez peut-être essayer d'utiliser la fonction d' annulation fournie par axios.

Avec lui, vous pouvez vous assurer que vous n'avez pas deux demandes similaires (ou plus, selon votre implémentation) dans un état en attente.

Ci-dessous, vous trouverez un petit exemple simplifié pour vous assurer que seule la dernière demande est traitée. Vous pouvez l'ajuster un peu pour le faire fonctionner comme un pool de requêtes

    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;


1 commentaires

Je ne pense pas que l'annulation de la demande précédente soit la meilleure solution. a) cela génère une erreur, c'est une surcharge que l'utilisateur doit gérer. b) la demande est toujours lancée, juste annulée plus tard.



1
votes

J'ai un problème similaire, grâce à mes recherches, il semble qu'il manque une bonne solution. Tout ce que j'ai vu, ce sont des solutions ad hoc, alors j'ouvre un problème pour axios, en espérant que quelqu'un puisse répondre à ma question https://github.com/axios/axios/issues/2118

Je trouve aussi cet article sur la limitation des requêtes Axios mais je n'ai pas essayé la solution qu'il suggérait.

Et j'ai une discussion à ce sujet. Ma mise en œuvre de la demande de debounce axios a laissé la promesse en attente pour toujours, y a-t-il un meilleur moyen?


0 commentaires

5
votes

Il est assez facile d'étrangler une demande axios elle-même. Le vrai casse-tête est de savoir comment gérer les promesses renvoyées par les demandes annulées. Qu'est-ce qui est considéré comme un comportement sain lorsqu'il s'agit de promesses renvoyées par une requête axios annulée? Devraient-ils rester en suspens pour toujours?

Je ne vois aucune solution parfaite à ce problème. Mais ensuite, j'arrive à une solution qui est une sorte de triche:

Que faire si nous ne limitons pas l'appel axios, mais plutôt la XMLHttpRequest réelle?

Cela rend les choses beaucoup plus faciles, car cela évite le problème des promesses et c'est plus facile à mettre en œuvre. L'idée est d'implémenter un cache pour les requêtes récentes, et si une nouvelle requête correspond à une récente, il vous suffit d'extraire le résultat du cache et d'ignorer la XMLHttpRequest.

En raison du fonctionnement des intercepteurs axios , l'extrait de code suivant peut être utilisé pour ignorer un certain appel XHR de manière conditionnelle:

// 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);
});


7 commentaires

Votre exemple est utile pour montrer comment fonctionnent les intercepteurs (je ne les ai pas compris moi-même) Mais je dirai que la promesse de retour en cache semble plus facile.


@Qiulang vous avez raison. ce que j'essaie de faire, c'est de mettre en cache la première promesse retournée après la demande. Juste que je le fais d'une manière spécifique à axios. La réponse de bergi à votre question montre comment écrire un utilitaire à usage général, mais vous devez encore décider quand utiliser ou non cet utilitaire. Le mien montre l'idée de base de la stratégie de mise en cache qui correspond au cas d'OP.


Mais honnêtement , je ne savais pas que ce retour est promesse en cache au début. Modification de la réponse pour supprimer cette ligne trompeuse.


J'aime cette solution ... pirater l'intercepteur pour s'assurer que les demandes qui correspondent à une condition peuvent être ignorées. Exactement ce que je cherchais..merci.


Je tiens à mentionner cependant qu'il pourrait y avoir un moyen plus simple d'arrêter la demande que le hack skipXHRError ici: github.com/axios/axios/issues/1497#issuecomment-404211504


@ManavM ^ c'est différent, cette approche n'annule que la requête XHR, mais la promesse de la requête axios se résoudra en réponse nulle. ma solution résoudra le résultat précédemment mis en cache.


Je vois ... Cependant, arrêter la requête XHR est exactement ce dont j'avais besoin. Votre solution agira essentiellement comme un système de mise en cache local construit par l'utilisateur et c'est vraiment cool, mais ce n'est pas quelque chose que je voudrais implémenter car la plupart des navigateurs se chargent de la mise en cache des demandes pour vous.



0
votes

J'en termine un, @hackape merci pour votre réponse, le code est le suivant:

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
   }
 });


0 commentaires

0
votes

Il existe des axios extensions très pratiques https://www.npmjs.com/package/axios-extensions qui peuvent ralentir, mettre en cache les requêtes et renvoyer automatiquement celles qui ont échoué.


0 commentaires