Wenn Sie jemals die JavaScript- 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. fetch (Video am Ende, wenn Sie das bevorzugen) Betrachten wir etwas ganz Grundlegendes mit einer einzigen Eingabe und einem Senden-Button. HTML-Formular <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 um einen Ereignis-Listener zum „Senden“-Ereignis des Formulars hinzuzufügen, das Standardverhalten zu verhindern und die Formulardaten mithilfe der API zu senden. JavaScript fetch 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 erstellt , , Und und am Ende des Handlers rufen wir die Methode des Ereignisses auf. fetch Aktion Methode Daten preventDefault() 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 und was auch immer, lassen Sie mich einfach sagen, ich weiß. Ich halte die bewusst einfach, da sie nicht im Vordergrund steht. Inhaltstyp fetch Das Hauptproblem hierbei ist . Diese Methode verhindert, dass der Browser das Standardverhalten ausführt, nämlich das Laden der neuen Seite und das Absenden des Formulars. event.preventDefault() 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 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. HTML 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 („ “). Jedes Mal, wenn wir die Funktion ausführen, können wir die Gesamtzahl erhöhen und die aktuelle Zahl erfassen, um die aktuelle Anfrage („ “) zu verfolgen. totalRequestCount handleSubmit thisRequestNumber Wenn eine gelöst wird, können wir die entsprechende Nummer in der Konsole protokollieren. fetch 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 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. fetch 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 UND eine konsistente Benutzeroberfläche! sexy Benutzererfahrung Wir müssen nur noch ein bisschen mehr Laufarbeit leisten. Wenn Sie sich die Dokumentation API ansehen, werden Sie feststellen, dass es möglich ist, einen Abruf mithilfe eines und der der abzubrechen. Es sieht ungefähr so aus: fetch AbortController signal fetch const controller = new AbortController(); fetch(url, { signal: controller.signal }); Durch die Bereitstellung des -Signals für die können wir die Anforderung jederzeit abbrechen, wenn die Methode des ausgelöst wird. AbortContoller fetch abort AbortContoller Ein klareres Beispiel finden Sie in der JavaScript-Konsole. Versuchen Sie, einen zu erstellen, die zu initiieren und dann sofort die Methode auszuführen. AbortController fetch abort 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. Vor diesem Hintergrund können wir einen zum Submit-Handler unseres Formulars hinzufügen. Die Logik wird wie folgt sein: AbortController Suchen Sie zunächst nach einem für alle vorherigen Anforderungen. Wenn einer vorhanden ist, brechen Sie ihn ab. AbortController Erstellen Sie als Nächstes einen für die aktuelle Anfrage, der bei nachfolgenden Anfragen abgebrochen werden kann. AbortController 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 , um Beziehungen zwischen jedem übermittelten DOM-Knoten und seinem jeweiligen zu speichern. Wenn ein Formular übermittelt wird, können wir die überprüfen und entsprechend aktualisieren. WeakMap <form> AbortController WeakMap 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 -Schlüssel ist eine bequeme Möglichkeit, dies zu tun. WeakMap Damit können wir das Signal des zur hinzufügen, alle vorherigen Controller abbrechen, neue hinzufügen und sie nach Abschluss löschen. AbortController fetch 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 ) und ein Protokoll von „7“ in der Konsole sehen. AbortController 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 einen Block hinzufügen und prüfen, ob der Name des Fehlers mit „ “ übereinstimmt: fetch .catch() 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 }); Schließen Dieser ganze Beitrag konzentrierte sich auf JavaScript-erweiterte Formulare, aber es ist wahrscheinlich eine gute Idee, jedes Mal, wenn Sie eine erstellen, einen 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. fetch AbortController 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 , würden diese Anfragen leider immer noch an Ihr Backend weitergeleitet und könnten eine Menge unnötiger Ressourcen verbrauchen. versendet Einige naive Lösungen bestehen möglicherweise darin, die Schaltfläche „Senden“ mithilfe von a zu deaktivieren , 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. entprellen 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 . Das sprengt den Rahmen dieses Beitrags, war aber erwähnenswert. Geschwindigkeitsbegrenzung 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. https://www.youtube.com/watch?v=w8ZIoLnh1Dc&embedable=true Vielen Dank fürs Lesen. Wenn Ihnen dieser Artikel gefallen hat, bitte . Es ist eine der besten Möglichkeiten, mich zu unterstützen. Du kannst auch oder wenn Sie wissen möchten, wann neue Artikel veröffentlicht werden. teilt es Melden Sie sich für meinen Newsletter an folge mir auf Twitter Ursprünglich veröffentlicht am austingil.com .