DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Uncategorized»Annuler les demandes de récupération en double dans les formulaires améliorés JavaScript
    Uncategorized

    Annuler les demandes de récupération en double dans les formulaires améliorés JavaScript

    février 7, 2023
    Annuler les demandes de récupération en double dans les formulaires améliorés JavaScript
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Si vous avez déjà utilisé JavaScript fetch API pour améliorer la soumission d’un formulaire, il y a de fortes chances que vous ayez accidentellement introduit un bogue de demande en double/condition de concurrence. Aujourd’hui, je vais vous expliquer le problème et mes recommandations pour l’éviter. (Il y a une vidéo à la fin si vous préférez cela.)

    Considérons un formulaire HTML très basique avec une seule entrée et un bouton d’envoi.

    <form method="post">
      <label for="name">Name</label>
      <input id="name" name="name" />
      <button>Submit</button>
    </form>

    Formulaire HTML de base avec une seule entrée et un bouton d'envoi

    Lorsque nous appuyons sur le bouton Soumettre, le navigateur effectue une actualisation complète de la page.

    Lorsque nous appuyons sur le bouton Soumettre, le navigateur effectue une actualisation complète de la page.

    Remarquez comment le navigateur se recharge après avoir cliqué sur le bouton Soumettre.

    L’actualisation de la page n’est pas toujours l’expérience que nous souhaitons offrir à nos utilisateurs. Une alternative courante consiste donc à utiliser JavaScript pour ajouter un écouteur d’événement à l’événement « soumettre » du formulaire, empêcher le comportement par défaut et soumettre les données du formulaire à l’aide de la fetch API.

    Une approche simpliste pourrait ressembler à l’exemple ci-dessous. Après le montage de la page (ou du composant), nous saisissons le nœud DOM du formulaire, ajoutons un écouteur d’événement qui construit un fetch request en utilisant l’action, la méthode et les données du formulaire, et à la fin du gestionnaire, nous appelons l’événement preventDefault() méthode.

    const form = document.querySelector('form');
    form.addEventListener('submit', handleSubmit);
    
    function handleSubmit(event) {
      const form = event.currentTarget;
      fetch(form.action, {
        method: form.method,
        body: new FormData(form)
      });
    
      event.preventDefault();
    }

    Maintenant, avant que les hotshots JavaScript ne commencent à me tweeter à propos de GET vs POST et du corps de la demande et du type de contenu et de tout le reste, permettez-moi de dire, je sais. je garde le fetch demande délibérément simple car ce n’est pas l’objectif principal.

    La question clé ici est la event.preventDefault(). Cette méthode empêche le navigateur d’exécuter le comportement par défaut de chargement de la nouvelle page et de soumission du formulaire.

    Maintenant, si nous regardons l’écran et que nous appuyons sur soumettre, nous pouvons voir que la page ne se recharge pas, mais nous voyons la requête HTTP dans notre onglet réseau.

    Si nous regardons l'écran et cliquons sur soumettre, nous pouvons voir que la page ne se recharge pas, mais nous voyons la requête HTTP dans notre onglet réseau.

    Notez que le navigateur n’effectue pas de rechargement complet de la page.

    Malheureusement, en utilisant JavaScript pour empêcher le comportement par défaut, nous avons en fait introduit un bogue que le comportement par défaut du navigateur n’a pas.

    Lorsque nous utilisons du HTML brut et que vous appuyez plusieurs fois très rapidement sur le bouton Soumettre, vous remarquerez que toutes les requêtes réseau, à l’exception de la plus récente, deviennent rouges. Cela indique qu’ils ont été annulés et que seule la demande la plus récente est honorée.

    Utilisez du HTML simple et vous écrasez le bouton d'envoi plusieurs fois très rapidement, vous remarquerez que toutes les requêtes réseau, à l'exception de la plus récente, deviennent rouges.

    Si nous comparons cela à l’exemple JavaScript, nous verrons que toutes les demandes sont envoyées et toutes se terminent sans qu’aucune ne soit annulée.

    Comparez cela à l'exemple JavaScript et voyez que toutes les demandes sont envoyées et toutes se terminent sans qu'aucune ne soit annulée.

    Cela peut être un problème, car même si chaque demande peut prendre un temps différent, elles peuvent être résolues dans un ordre différent de celui dans lequel elles ont été lancées. Cela signifie que si nous ajoutons des fonctionnalités à la résolution de ces demandes, nous pourrions avoir un comportement inattendu.

    Par exemple, nous pourrions créer une variable à incrémenter pour chaque requête (totalRequestCount). Chaque fois que nous exécutons le handleSubmit fonction, nous pouvons incrémenter le nombre total ainsi que capturer le nombre actuel pour suivre la demande actuelle (thisRequestNumber). Lorsqu’un fetch demande résolue, nous pouvons enregistrer son numéro correspondant dans la console.

    const form = document.querySelector('form');
    form.addEventListener('submit', handleSubmit);
    
    let totalRequestCount = 0
    
    function handleSubmit(event) {
      totalRequestCount += 1
      const thisRequestNumber = totalRequestCount 
      const form = event.currentTarget;
      fetch(form.action, {
        method: form.method,
        body: new FormData(form)
      }).then(() => {
        console.log(thisRequestNumber)
      })
      event.preventDefault();
    }

    Maintenant, si nous frappons ce bouton de soumission plusieurs fois, nous pourrions voir différents numéros imprimés sur la console dans le désordre : 2, 3, 1, 4, 5. Cela dépend de la vitesse du réseau, mais je pense que nous pouvons tous être d’accord que ce n’est pas idéal.

    Considérez un scénario où un utilisateur déclenche plusieurs fetch requêtes en succession rapprochée et une fois terminées, votre application met à jour la page avec leurs modifications. L’utilisateur pourrait finalement voir des informations inexactes en raison de demandes résolues dans le désordre.

    Il ne s’agit pas d’un problème dans le monde non-JavaScript, car le navigateur annule toute requête précédente et charge la page une fois la requête la plus récente terminée, en chargeant la version la plus récente. Mais les rafraîchissements de page ne sont pas aussi sexy.

    La bonne nouvelle pour les amateurs de JavaScript est que nous pouvons avoir à la fois une expérience utilisateur sexy ET une interface utilisateur cohérente !

    Nous avons juste besoin de faire un peu plus de démarches.

    Si vous regardez le fetch documentation de l’API, vous verrez qu’il est possible d’abandonner une récupération à l’aide d’un AbortController et le signal propriété de la fetch options. Cela ressemble à ceci :

    const controller = new AbortController();
    fetch(url, { signal: controller.signal });

    En fournissant le AbortContollersignal au fetch demande, nous pouvons annuler la demande à tout moment AbortContollerc’est abort méthode est déclenchée.

    Vous pouvez voir un exemple plus clair dans la console JavaScript. Essayez de créer un AbortControllerinitiant la fetch demande, puis exécutez immédiatement la abort méthode.

    const controller = new AbortController();
    fetch('', { signal: controller.signal });
    controller.abort()

    Vous devriez immédiatement voir une exception imprimée sur la console. Dans les navigateurs Chromium, il devrait dire, « Uncaught (in promise) DOMException: L’utilisateur a abandonné une demande. » Et si vous explorez l’onglet Réseau, vous devriez voir une demande ayant échoué avec le texte d’état « (annulé) ».

    Non intercepté (en promesse) DOMException : l'utilisateur a abandonné une requête.

    Dans cet esprit, nous pouvons ajouter un AbortController au gestionnaire de soumission de notre formulaire. La logique sera la suivante :

    • Tout d’abord, recherchez un AbortController pour toute demande antérieure. S’il en existe un, annulez-le.
    • Ensuite, créez un AbortController pour la requête en cours qui peut être abandonnée sur les requêtes suivantes.
    • Enfin, lorsqu’une requête est résolue, supprimez son correspondant AbortController.

    Il y a plusieurs façons de le faire, mais j’utiliserai un WeakMap pour stocker les relations entre chaque soumission <form> nœud DOM et ses nœuds respectifs AbortController. Lorsqu’un formulaire est soumis, nous pouvons vérifier et mettre à jour WeakMap par conséquent.

    const pendingForms = new WeakMap();
    
    function handleSubmit(event) {
      const form = event.currentTarget;
      const previousController = pendingForms.get(form);
    
      if (previousController) {
        previousController.abort();
      }
        
      const controller = new AbortController();
      pendingForms.set(form, controller);
    
      fetch(form.action, {
        method: form.method,
        body: new FormData(form),
        signal: controller.signal,
      }).then(() => {
        pendingForms.delete(form);
      });
      event.preventDefault();
    }
    
    const forms = document.querySelectorAll('form');
    for (const form of forms) {
      form.addEventListener('submit', handleSubmit);
    }

    L’essentiel est de pouvoir associer un contrôleur d’abandon à son formulaire correspondant. Utiliser le nœud DOM du formulaire comme WeakMapLa clé de est un moyen pratique de le faire. Avec cela en place, nous pouvons ajouter le AbortControllersignal au fetch demande, abandonnez tous les contrôleurs précédents, ajoutez-en de nouveaux et supprimez-les à la fin.

    J’espère que tout cela a du sens.

    Maintenant, si nous frappons plusieurs fois le bouton d’envoi de ce formulaire, nous pouvons voir que toutes les demandes d’API, à l’exception de la plus récente, sont annulées.

    Frappez le bouton d'envoi de ce formulaire plusieurs fois pour voir que toutes les demandes d'API, à l'exception de la plus récente, sont annulées.

    Cela signifie que toute fonction répondant à cette réponse HTTP se comportera davantage comme prévu. Maintenant, si nous utilisons la même logique de comptage et de journalisation que nous avons ci-dessus, nous pouvons écraser le bouton d’envoi sept fois et verrions six exceptions (en raison de la AbortController) et un journal de « 7 » dans la console. Si nous soumettons à nouveau et laissons suffisamment de temps pour que la demande soit résolue, nous verrons « 8 » dans la console. Et si nous écrasons le bouton Soumettre un tas de fois, encore une fois, nous continuerons à voir les exceptions et le nombre de requêtes finales dans le bon ordre.

    Si vous souhaitez ajouter un peu plus de logique pour éviter de voir DOMExceptions dans la console lorsqu’une requête est abandonnée, vous pouvez ajouter un .catch() bloquer après votre fetch requête et vérifiez si le nom de l’erreur correspond à « AbortError“ :

    fetch(url, {
      signal: controller.signal,
    }).catch((error) => {
      // If the request was aborted, do nothing
      if (error.name === 'AbortError') return;
      // Otherwise, handle the error here or throw it back to the console
      throw error
    });

    Fermeture

    Tout cet article était axé sur les formulaires améliorés par JavaScript, mais c’est probablement une bonne idée d’inclure un AbortController chaque fois que vous créez un fetch demande. C’est vraiment dommage qu’il ne soit pas déjà intégré à l’API, mais j’espère que cela vous montre une bonne méthode pour l’inclure.

    Il convient également de mentionner que cette approche n’empêche pas l’utilisateur de spammer le bouton d’envoi plusieurs fois. Le bouton est toujours cliquable et la demande se déclenche toujours, cela fournit simplement un moyen plus cohérent de traiter les réponses.

    Malheureusement, si un utilisateur fait spam un bouton d’envoi, ces demandes iraient toujours vers votre backend et pourraient utiliser un tas de ressources inutiles.

    Certaines solutions naïves peuvent désactiver le bouton d’envoi, utiliser un anti-rebond ou créer de nouvelles demandes uniquement après la résolution des précédentes. Je n’aime pas ces options car elles reposent sur le ralentissement de l’expérience de l’utilisateur et ne fonctionnent que du côté client. Ils ne traitent pas les abus via des requêtes scriptées.

    Pour traiter les abus d’un trop grand nombre de requêtes adressées à votre serveur, vous souhaiterez probablement mettre en place une limitation de débit. Cela dépasse le cadre de cet article, mais cela valait la peine d’être mentionné. Il convient également de mentionner que la limitation du débit ne résout pas le problème initial des demandes en double, des conditions de concurrence et des mises à jour incohérentes de l’interface utilisateur. Idéalement, nous devrions utiliser les deux pour couvrir les deux extrémités.

    Quoi qu’il en soit, c’est tout ce que j’ai pour aujourd’hui. Si vous voulez regarder une vidéo qui traite du même sujet, regardez ceci.

    Merci beaucoup d’avoir lu. Si vous avez aimé cet article, merci de le partager.

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.