Rufen Sie event.preventDefault() vor dem ersten await auf, führen Sie Ihre asynchrone Validierung aus und verwenden Sie anschließend form.requestSubmit(), um einen neuen Übermittlungsversuch zu starten. Eine einmalige Freigabe ermöglicht diesen zweiten Versuch, ohne die asynchrone Prüfung zu wiederholen.

Dieses Tutorial richtet sich an Personen, die eine kleine Browseroberfläche auf einem Mac bearbeiten. Sie erstellen ein lokales HTML-Beispiel, in dem ein Formular einen Dialog erst schließt, nachdem eine simulierte Beschriftungsprüfung erfolgreich war. Dasselbe Muster zur Ereignisbehandlung lässt sich auch auf gewöhnliche Formulare anwenden, die zu einer anderen Seite navigieren oder eine Anfrage senden.

Warum verzögert ein asynchroner Submit-Handler die Übermittlung nicht?

Der Browser wartet nicht auf das Promise, das ein Event-Listener zurückgibt. Der DOM-Algorithmus zur Ereignisverteilung ruft die Listener auf und fährt fort; der HTML-Algorithmus zur Formularübermittlung prüft, ob das Ereignis submit abgebrochen wurde, bevor er fortfährt. async zum Listener hinzuzufügen, führt in diesem Ablauf keine Wartephase ein. DOM-Standard: Ereignisverteilung, HTML-Standard: Algorithmus zur Formularübermittlung

Eine asynchrone Funktion läuft synchron bis zu ihrem ersten await und wird danach asynchron fortgesetzt. Deshalb muss der Abbruch vor dem ersten await erfolgen, selbst wenn das abgewartete Promise bereits erfüllt ist. Ein anschließender Aufruf von preventDefault() kann eine Übermittlungsentscheidung, die der Browser bereits getroffen hat, nicht rückgängig machen. MDN: asynchrone Funktion

Durch den Abbruch wird der ursprüngliche Versuch beendet; er wird nicht angehalten, um später fortgesetzt zu werden. Nach erfolgreicher Validierung muss Ihr Code ausdrücklich eine neue Übermittlung anfordern. Außerdem verhindert preventDefault() nicht die Weitergabe des Ereignisses, sodass andere Listener es weiterhin beobachten können. MDN: Event.preventDefault()

Hören Sie auf das Ereignis submit des Formulars. Dadurch werden Übermittlungen über eine Schaltfläche, unterstützte implizite Übermittlungen wie das Drücken der Eingabetaste sowie Aufrufe von requestSubmit() erfasst. Ein Click-Handler nur für die Schaltfläche deckt nicht alle Auslöser ab. Die native Constraint-Validierung wird normalerweise zuerst ausgeführt: Ein ungültiges Pflichtfeld verhindert die Übermittlung, ohne submit auszulösen. MDN: Submit-Ereignis von HTMLFormElement

Welche Methode erhält das Übermittlungsverhalten des Formulars?

Verwenden Sie requestSubmit(), wenn nach erfolgreicher Prüfung der normale Lebenszyklus des Formulars fortgesetzt werden soll. Ein direkter Aufruf von submit() überspringt sowohl die Constraint-Validierung als auch das Ereignis submit. Diese Abkürzung verändert den Ablauf, den Ihr asynchroner Handler erhalten soll. MDN: HTMLFormElement.submit()

Vorgehensweise Was geschieht Eignung in diesem Fall
Ein Promise vom Listener zurückgeben Die Ereignisverteilung wird ohne Abwarten fortgesetzt Verzögert die Standardaktion nicht
Abbrechen, dann submit() aufrufen Die Übermittlung umgeht die native Validierung und Submit-Listener Verändert den Ablauf
Abbrechen, dann requestSubmit(submitter) aufrufen Die native Validierung und das Submit-Ereignis werden erneut ausgeführt Erhält den vorgesehenen Übermittlungsweg

Erfassen Sie event.submitter, bevor Sie warten. Wenn Sie diese Schaltfläche an requestSubmit() übergeben, bleibt ihre Funktion erhalten, einschließlich etwaiger Übersteuerungen der Übermittlung sowie ihres Beitrags zu Name und Wert, sofern zutreffend. Wenn es keine Submit-Schaltfläche gab, rufen Sie requestSubmit() ohne Argument auf. Eine übergebene Schaltfläche muss weiterhin zum Formular gehören; eine ungültige Schaltfläche oder eine Schaltfläche, die zu einem anderen Formular gehört, löst eine Ausnahme aus. MDN: HTMLFormElement.requestSubmit()

Die neue Übermittlung löst Ihren Listener erneut aus. Das folgende Beispiel verwendet daher zwei getrennte Zustandswerte: pending verhindert überlappende asynchrone Prüfungen, während allowNextSubmit genau einen synchronen Durchlauf durch den Handler erlaubt. Ein dauerhaft gesetztes Flag „validiert“ wäre zu weit gefasst, denn spätere Änderungen könnten ein früheres Ergebnis wiederverwenden.

Erstellen Sie ein vollständiges lokales Beispiel auf Ihrem Mac

Speichern Sie das folgende vollständige Dokument in einem reinen Texteditor für Code unter async-form.html und öffnen Sie es anschließend in einem aktuellen Browser auf Ihrem Mac. Bibliotheken, Imports, Server oder Installationen sind nicht erforderlich. Die Spezifikation führt Unterstützung für requestSubmit() ab Safari 16 auf. HTML-Standard: Formular-APIs

Das Beispiel verwendet method="dialog", damit die Standardaktion lokal leicht zu erkennen ist. Bei erfolgreicher Übermittlung wird der umschließende Dialog geschlossen und sein returnValue auf den Wert der Submit-Schaltfläche gesetzt; die Formularfelder werden nicht übertragen. Die Schaltfläche zum Öffnen ruft showModal() auf, um den Dialog anzuzeigen. MDN: Dialogelement, MDN: HTMLDialogElement.showModal()

Der Validator ist absichtlich simuliert: Nach einer kurzen Verzögerung akzeptiert er Beschriftungen mit mindestens drei Zeichen nach dem Entfernen umgebender Leerzeichen, weist die exakt kleingeschriebene Beschriftung taken zurück und löst bei der exakt kleingeschriebenen Beschriftung error einen Fehler aus. Die Verzögerung veranschaulicht den asynchronen Abschluss; sie ist keine Leistungsmessung.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Asynchronous form validation</title>
</head>
<body>
  <h1>Label preview</h1>
  <button id="open" type="button">Open form</button>
  <p id="outcome" role="status"></p>

  <dialog id="editor" aria-labelledby="dialog-title">
    <form id="label-form" method="dialog">
      <h2 id="dialog-title">Choose a label</h2>
      <label for="label">Label</label>
      <input id="label" name="label" required autofocus>
      <button type="submit" value="accepted">Continue</button>
      <button id="cancel" type="button">Cancel</button>
      <p id="status" role="status"></p>
    </form>
  </dialog>

  <script>
    const dialog = document.getElementById("editor");
    const form = document.getElementById("label-form");
    const input = document.getElementById("label");
    const status = document.getElementById("status");
    const outcome = document.getElementById("outcome");
    const openButton = document.getElementById("open");
    const cancelButton = document.getElementById("cancel");

    let pending = false;
    let allowNextSubmit = false;
    let revision = 0;

    async function validateLabel(value) {
      await new Promise(resolve => setTimeout(resolve, 700));
      const label = value.trim();
      if (label === "error") {
        throw new Error("Synthetic validation failure");
      }
      return label.length >= 3 && label !== "taken";
    }

    openButton.addEventListener("click", () => {
      revision += 1;
      dialog.returnValue = "";
      status.textContent = "";
      outcome.textContent = "";
      dialog.showModal();
    });

    input.addEventListener("input", () => {
      revision += 1;
    });

    cancelButton.addEventListener("click", () => dialog.close());

    dialog.addEventListener("close", () => {
      revision += 1;
      outcome.textContent = "Dialog closed. Return value: " +
        (dialog.returnValue || "(empty)");
    });

    form.addEventListener("submit", async event => {
      if (allowNextSubmit) {
        allowNextSubmit = false;
        return;
      }

      event.preventDefault();
      if (pending) return;

      pending = true;
      const submitter = event.submitter;
      const value = input.value;
      const checkedRevision = revision;
      status.textContent = "Checking label...";

      try {
        const valid = await validateLabel(value);
        if (!dialog.open) return;

        if (revision !== checkedRevision || input.value !== value) {
          status.textContent = "The label changed. Submit again.";
          return;
        }

        if (!valid) {
          status.textContent =
            "Use at least three characters and avoid taken.";
          return;
        }

        status.textContent = "Check passed. Submission requested.";
        allowNextSubmit = true;
        try {
          if (submitter) form.requestSubmit(submitter);
          else form.requestSubmit();
        } finally {
          allowNextSubmit = false;
        }
      } catch (error) {
        if (dialog.open && revision === checkedRevision) {
          status.textContent = "The check failed. Try again.";
        }
        console.error(error);
      } finally {
        pending = false;
      }
    });
  </script>
</body>
</html>

Das Browserverhalten des Beispiels ergibt sich aus dem dokumentierten Ablauf. Eine kleine Node.js-Prüfung mit EventTarget bestätigte den Zeitunterschied: Ein synchroner Abbruch änderte das Ergebnis der Ereignisverteilung, ein Abbruch nach await kam dafür zu spät. Das vollständige Dialogbeispiel wurde hier nicht in einem Mac-Browser ausgeführt.

Wie erhält die Freigabe die Reihenfolge und berücksichtigt Änderungen?

Beim ersten Übermittlungsversuch bricht der Handler sofort ab und erfasst sowohl die Beschriftung als auch die auslösende Schaltfläche. Während die Prüfung läuft, wird auch ein weiteres Submit-Ereignis abgebrochen, aber es startet keine weitere Validierung. So lösen schnell aufeinanderfolgende Übermittlungen keine überlappenden Prüfungen aus.

Wenn die Prüfung abgeschlossen ist, stellt der Handler fest, ob der Dialog noch geöffnet ist und ob die Beschriftung unverändert geblieben ist. Der Revisionszähler erkennt Änderungen durch den Benutzer auch dann, wenn dieser den Text ändert und anschließend wiederherstellt. Der Wertvergleich erfasst außerdem eine direkte Wertänderung, durch die im Feld ein anderer Text steht.

Bei erfolgreicher Prüfung wird allowNextSubmit unmittelbar vor dem Aufruf von requestSubmit() gesetzt. Diese Methode durchläuft erneut die native Validierung und den Submit-Ereignispfad. Beim erneuten Aufruf des Listeners verbraucht dieser die Freigabe und kehrt zurück, ohne abzubrechen. Anschließend kann der Browser die Standardaktion zum Schließen des Dialogs ausführen. Diese Reihenfolge entspricht der Sequenz des Standards, bei der die Validierung vor dem Submit-Ereignis erfolgt. HTML-Standard: Algorithmus zur Formularübermittlung

Das innere finally löscht die Freigabe, selbst wenn kein Submit-Ereignis auftritt oder die Methode einen Fehler auslöst. Das äußere finally setzt pending nach einer Ablehnung, einer Ausnahme, einem veralteten Ergebnis oder einem erfolgreichen erneuten Versuch zurück. Nach einem erfolglosen Versuch bleibt das Formular also für eine weitere Prüfung verfügbar.

Beim Schließen des Dialogs wird auch die Revision erhöht. Ein Ergebnis aus einer früheren Öffnung kann keine spätere Öffnung freigeben. Für dieses eigenständige Dokument decken die Prüfungen alle verfügbaren Interaktionspfade ab. Wenn Sie weitere Felder oder Skripte hinzufügen, die diese aktualisieren, erweitern Sie die Erfassung und Revisionsbehandlung auf alle Werte, von denen der Validator abhängt.

Testen Sie die Pfade für Erfolg, Ablehnung und Unterbrechung

Führen Sie diese Prüfungen im lokalen Browserbeispiel durch. Die erwarteten Ergebnisse folgen aus dem bereitgestellten Code; sie werden nicht als Browser-Testergebnisse ausgegeben.

Aktion Erwartetes Ergebnis
Lassen Sie die Beschriftung leer und wählen Sie Continue Die native Validierung des Pflichtfelds verhindert die Übermittlung, bevor der asynchrone Handler ausgeführt wird
Geben Sie ab ein und wählen Sie Continue Der Dialog bleibt geöffnet und zeigt nach der Prüfung die Ablehnungsmeldung an
Geben Sie taken ein Die simulierte Regel weist die Eingabe zurück
Geben Sie sample ein Der Dialog bleibt während der Prüfung geöffnet und wird danach mit dem Rückgabewert accepted geschlossen
Geben Sie error ein Die Fehlermeldung erscheint, und eine weitere Übermittlung bleibt möglich
Senden Sie eine Beschriftung ab und bearbeiten Sie sie während der Prüfung Das Ergebnis wird verworfen und das Formular fordert zu einer erneuten Übermittlung auf
Senden Sie während der Prüfung wiederholt ab Nur der erste Versuch startet eine asynchrone Prüfung
Wählen Sie während der Prüfung Cancel Der Dialog wird ohne Abwarten des ausstehenden Ergebnisses geschlossen

Versuchen Sie auch, mit der Eingabetaste abzusenden, während das Eingabefeld fokussiert ist. Die implizite Übermittlung hängt von der Formularstruktur und den Plattformkonventionen ab; der Grund für die Behandlung von submit ist, alle tatsächlich unterstützten Interaktionen abzudecken, die eine Formularübermittlung auslösen. MDN: Submit-Ereignis von HTMLFormElement

Was ändert sich, wenn das Formular eine normale Anfrage sendet?

Behalten Sie bei einem bestehenden Formular, das Anfragen sendet, dessen tatsächliches action, method, Feldnamen, Schaltflächenwerte und Übersteuerungen der Übermittlung bei. Ersetzen Sie den simulierten Validator durch Ihre tatsächliche asynchrone Prüfung. Das Öffnen und Schließen des Dialogs ist spezifisch für diese Demonstration; die Sequenz aus Abbrechen, Validieren und erneuter Übermittlung gehört in den Submit-Handler des Formulars.

Lassen Sie die Felder für die endgültige Übermittlung verfügbar. Während des Wartens Steuerelemente zu deaktivieren, erfordert Sorgfalt: Deaktivierte Felder werden nicht in die Übermittlungseintragsliste aufgenommen, die der Browser erstellt. Stattdessen lässt das Beispiel Änderungen zu und verwirft veraltete Validierungsergebnisse. HTML-Standard: Erstellen der Eintragsliste

Falls weitere Submit-Listener vorhanden sind, beobachten sie sowohl den ersten abgebrochenen Versuch als auch den späteren Versuch. Stimmen Sie sie so ab, dass sie dieselbe Arbeit nicht doppelt erledigen und validierte Werte während des freigegebenen Durchlaufs nicht ändern. requestSubmit() garantiert außerdem keinen Abschluss: Ein anderer Listener kann die Übermittlung abbrechen, und native Einschränkungen können sie blockieren. Der Rückgabewert ist undefined, kein Promise für den Abschluss. MDN: HTMLFormElement.requestSubmit()

Die zentrale Vereinbarung ist präzise: synchron abbrechen, eine klar definierte Menge von Werten validieren, veraltete Ergebnisse verwerfen und genau einen neuen nativen Übermittlungsversuch freigeben. Dadurch erhält die asynchrone Validierung einen klaren Platz im Ablauf, während der Formularlebenszyklus des Browsers erhalten bleibt.

Quellen