paint-brush
Eine Anleitung zum Abbrechen doppelter Abrufanforderungen in JavaScript-erweiterten Formularenvon@austingil
2,570 Lesungen
2,570 Lesungen

Eine Anleitung zum Abbrechen doppelter Abrufanforderungen in JavaScript-erweiterten Formularen

von Austin Gil8m2023/02/10
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

Es besteht eine gute Chance, dass Sie versehentlich einen Duplikat-Request-/Race-Condition-Fehler eingeführt haben. Heute werde ich Sie durch das Problem und meine Empfehlungen führen, um es zu vermeiden. Das Hauptproblem hierbei ist „event.preventDefault()“. Diese Methode verhindert, dass der Browser das Standardverhalten ausführt, nämlich das Laden der neuen Seite und das Absenden des Formulars.
featured image - Eine Anleitung zum Abbrechen doppelter Abrufanforderungen in JavaScript-erweiterten Formularen
Austin Gil HackerNoon profile picture

Wenn Sie jemals die JavaScript- fetch API zum Verbessern einer Formularübermittlung verwendet haben, besteht eine gute Chance, dass Sie versehentlich einen Fehler bei doppelten Anforderungen/Race-Conditions eingeführt haben. Heute werde ich Sie durch das Problem und meine Empfehlungen führen, um es zu vermeiden.


(Video am Ende, wenn Sie das bevorzugen)


Betrachten wir etwas ganz Grundlegendes HTML-Formular mit einer einzigen Eingabe und einem Senden-Button.


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


Wenn wir auf die Schaltfläche „Senden“ klicken, führt der Browser eine Aktualisierung der gesamten Seite durch.


Beachten Sie, wie der Browser neu geladen wird, nachdem auf die Schaltfläche „Senden“ geklickt wurde.


Die Seitenaktualisierung ist nicht immer das Erlebnis, das wir unseren Benutzern bieten möchten, daher ist die Verwendung eine gängige Alternative JavaScript um einen Ereignis-Listener zum „Senden“-Ereignis des Formulars hinzuzufügen, das Standardverhalten zu verhindern und die Formulardaten mithilfe der fetch API zu senden.


Ein vereinfachter Ansatz könnte wie das folgende Beispiel aussehen.


Nachdem die Seite (oder Komponente) bereitgestellt wurde, greifen wir auf den DOM-Knoten des Formulars zu und fügen einen Ereignis-Listener hinzu, der mithilfe des Formulars eine fetch erstellt Aktion , Methode , Und Daten und am Ende des Handlers rufen wir die Methode preventDefault() des Ereignisses auf.


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


Bevor jetzt irgendwelche JavaScript-Hotshots anfangen, über GET vs. POST und den Anforderungstext zu twittern Inhaltstyp und was auch immer, lassen Sie mich einfach sagen, ich weiß. Ich halte die fetch bewusst einfach, da sie nicht im Vordergrund steht.


Das Hauptproblem hierbei ist event.preventDefault() . Diese Methode verhindert, dass der Browser das Standardverhalten ausführt, nämlich das Laden der neuen Seite und das Absenden des Formulars.


Wenn wir nun auf den Bildschirm schauen und auf „Senden“ klicken, sehen wir, dass die Seite nicht neu geladen wird, aber wir sehen die HTTP-Anfrage in unserem Netzwerk-Tab.


Beachten Sie, dass der Browser nicht die gesamte Seite neu lädt.


Leider haben wir durch die Verwendung von JavaScript zur Verhinderung des Standardverhaltens tatsächlich einen Fehler eingeführt, den das Standardverhalten des Browsers nicht aufweist.


Wenn wir plain verwenden HTML Wenn Sie die Schaltfläche „Senden“ ein paar Mal sehr schnell drücken, werden Sie feststellen, dass alle Netzwerkanfragen außer der letzten rot werden. Dies weist darauf hin, dass sie storniert wurden und nur die letzte Anfrage berücksichtigt wird.


Wenn wir das mit dem JavaScript-Beispiel vergleichen, werden wir sehen, dass alle Anfragen gesendet wurden und alle vollständig sind, ohne dass eine Anfrage abgebrochen wurde.


Dies kann ein Problem darstellen, da zwar jede Anfrage unterschiedlich lange dauern kann, sie jedoch möglicherweise in einer anderen Reihenfolge als der Reihenfolge, in der sie initiiert wurde, gelöst wird. Das heißt, wenn wir der Lösung dieser Anfragen Funktionen hinzufügen, kann es zu unerwartetem Verhalten kommen.


Als Beispiel könnten wir eine Variable erstellen, die für jede Anfrage erhöht wird („ totalRequestCount “). Jedes Mal, wenn wir die Funktion handleSubmit ausführen, können wir die Gesamtzahl erhöhen und die aktuelle Zahl erfassen, um die aktuelle Anfrage („ thisRequestNumber “) zu verfolgen.


Wenn eine fetch gelöst wird, können wir die entsprechende Nummer in der Konsole protokollieren.


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


Wenn wir nun mehrmals auf die Schaltfläche „Senden“ drücken, werden auf der Konsole möglicherweise verschiedene Zahlen in falscher Reihenfolge angezeigt: 2, 3, 1, 4, 5. Das hängt von der Netzwerkgeschwindigkeit ab, aber ich denke, wir sind uns alle einig dass das nicht ideal ist.


Stellen Sie sich ein Szenario vor, in dem ein Benutzer mehrere fetch kurz hintereinander auslöst und Ihre Anwendung nach Abschluss die Seite mit ihren Änderungen aktualisiert. Der Benutzer könnte letztendlich ungenaue Informationen sehen, weil Anfragen nicht in der richtigen Reihenfolge bearbeitet werden.


In der Nicht-JavaScript-Welt stellt dies kein Problem dar, da der Browser alle vorherigen Anfragen abbricht und die Seite lädt, nachdem die letzte Anfrage abgeschlossen ist, wobei die aktuellste Version geladen wird. Aber Seitenaktualisierungen sind nicht so sexy.


Die gute Nachricht für JavaScript-Liebhaber ist, dass wir beides haben können sexy Benutzererfahrung UND eine konsistente Benutzeroberfläche!


Wir müssen nur noch ein bisschen mehr Laufarbeit leisten.


Wenn Sie sich die Dokumentation fetch API ansehen, werden Sie feststellen, dass es möglich ist, einen Abruf mithilfe eines AbortController und der signal der fetch abzubrechen. Es sieht ungefähr so aus:


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


Durch die Bereitstellung des AbortContoller -Signals für die fetch können wir die Anforderung jederzeit abbrechen, wenn die abort Methode des AbortContoller ausgelöst wird.


Ein klareres Beispiel finden Sie in der JavaScript-Konsole. Versuchen Sie, einen AbortController zu erstellen, die fetch zu initiieren und dann sofort die abort Methode auszuführen.


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


Sie sollten sofort eine Ausnahme sehen, die auf der Konsole ausgegeben wird. In Chromium-Browsern sollte es lauten: „Uncaught (in versprochen) DOMException: Der Benutzer hat eine Anfrage abgebrochen.“ Und wenn Sie die Registerkarte „Netzwerk“ erkunden, sollten Sie eine fehlgeschlagene Anfrage mit dem Statustext „(abgebrochen)“ sehen.


Die Chrome-Entwicklungstools wurden mit geöffneter JavaScript-Konsole für das Netzwerk geöffnet. In der Konsole befindet sich der Code „const controller = new AbortController();fetch('', { signal: controller.signal });controller.abort()“, gefolgt von der Ausnahme „Uncaught (in Promise) DOMException: The Der Benutzer hat eine Anfrage abgebrochen. Im Netzwerk gibt es eine Anfrage an „localhost“ mit dem Statustext „(abgebrochen)“

Vor diesem Hintergrund können wir einen AbortController zum Submit-Handler unseres Formulars hinzufügen. Die Logik wird wie folgt sein:


  • Suchen Sie zunächst nach einem AbortController für alle vorherigen Anforderungen. Wenn einer vorhanden ist, brechen Sie ihn ab.


  • Erstellen Sie als Nächstes einen AbortController für die aktuelle Anfrage, der bei nachfolgenden Anfragen abgebrochen werden kann.


  • Wenn eine Anfrage schließlich aufgelöst wird, entfernen Sie den entsprechenden AbortController .


Es gibt mehrere Möglichkeiten, dies zu tun, aber ich verwende eine WeakMap , um Beziehungen zwischen jedem übermittelten <form> DOM-Knoten und seinem jeweiligen AbortController zu speichern. Wenn ein Formular übermittelt wird, können wir die WeakMap überprüfen und entsprechend aktualisieren.


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


Der Schlüssel liegt darin, einen Abbruch-Controller mit dem entsprechenden Formular verknüpfen zu können. Die Verwendung des DOM-Knotens des Formulars als WeakMap -Schlüssel ist eine bequeme Möglichkeit, dies zu tun.


Damit können wir das Signal des AbortController zur fetch hinzufügen, alle vorherigen Controller abbrechen, neue hinzufügen und sie nach Abschluss löschen.


Hoffentlich macht das alles Sinn.


Wenn wir nun mehrmals auf die Schaltfläche „Senden“ dieses Formulars drücken, können wir sehen, dass alle API-Anfragen außer der letzten abgebrochen werden.


Das bedeutet, dass sich jede Funktion, die auf diese HTTP-Antwort antwortet, eher so verhält, wie Sie es erwarten würden.


Wenn wir nun dieselbe Zähl- und Protokollierungslogik wie oben verwenden, können wir die Senden-Schaltfläche sieben Mal drücken und würden sechs Ausnahmen (aufgrund des AbortController ) und ein Protokoll von „7“ in der Konsole sehen.


Wenn wir die Anfrage erneut einreichen und genügend Zeit für die Lösung einplanen, wird in der Konsole „8“ angezeigt. Und wenn wir mehrmals auf die Schaltfläche „Senden“ drücken, werden die Ausnahmen und die endgültige Anzahl der Anfragen weiterhin in der richtigen Reihenfolge angezeigt.


Wenn Sie weitere Logik hinzufügen möchten, um zu vermeiden, dass DOMExceptions in der Konsole angezeigt werden, wenn eine Anfrage abgebrochen wird, können Sie nach Ihrer fetch einen .catch() Block hinzufügen und prüfen, ob der Name des Fehlers mit „ AbortError “ übereinstimmt:


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

Schließen

Dieser ganze Beitrag konzentrierte sich auf JavaScript-erweiterte Formulare, aber es ist wahrscheinlich eine gute Idee, jedes Mal, wenn Sie eine fetch erstellen, einen AbortController einzuschließen. Es ist wirklich schade, dass es nicht bereits in die API integriert ist. Aber hoffentlich zeigt Ihnen dies eine gute Methode, es einzubinden.


Erwähnenswert ist auch, dass dieser Ansatz den Benutzer nicht davon abhält, mehrmals auf die Schaltfläche „Senden“ zu spammen. Die Schaltfläche ist weiterhin anklickbar und die Anfrage wird weiterhin ausgelöst. Dies bietet lediglich eine konsistentere Möglichkeit, mit Antworten umzugehen.


Wenn ein Benutzer eine Absenden-Schaltfläche als Spam versendet , würden diese Anfragen leider immer noch an Ihr Backend weitergeleitet und könnten eine Menge unnötiger Ressourcen verbrauchen.


Einige naive Lösungen bestehen möglicherweise darin, die Schaltfläche „Senden“ mithilfe von a zu deaktivieren entprellen , oder neue Anfragen erst erstellen, nachdem vorherige gelöst wurden. Ich mag diese Optionen nicht, weil sie darauf basieren, die Benutzererfahrung zu verlangsamen und nur auf der Clientseite funktionieren.


Sie bekämpfen Missbrauch nicht über skriptgesteuerte Anfragen.


Um Missbrauch durch zu viele Anfragen an Ihren Server zu verhindern, möchten Sie wahrscheinlich welche einrichten Geschwindigkeitsbegrenzung . Das sprengt den Rahmen dieses Beitrags, war aber erwähnenswert.


Erwähnenswert ist auch, dass die Ratenbegrenzung das ursprüngliche Problem doppelter Anfragen, Race Conditions und inkonsistenter UI-Updates nicht löst. Idealerweise sollten wir beide verwenden, um beide Enden abzudecken.


Jedenfalls ist das alles, was ich für heute habe. Wenn Sie sich ein Video ansehen möchten, das dasselbe Thema behandelt, schauen Sie sich dieses an.

Vielen Dank fürs Lesen. Wenn Ihnen dieser Artikel gefallen hat, bitte teilt es . Es ist eine der besten Möglichkeiten, mich zu unterstützen. Du kannst auch Melden Sie sich für meinen Newsletter an oder folge mir auf Twitter wenn Sie wissen möchten, wann neue Artikel veröffentlicht werden.


Ursprünglich veröffentlicht am austingil.com .