Wywołaj event.preventDefault() przed pierwszym await, przeprowadź asynchroniczną walidację, a następnie użyj form.requestSubmit(), aby rozpocząć nową próbę wysłania formularza. Jednorazowa blokada pozwala tej drugiej próbie przejść dalej bez ponownego wykonywania asynchronicznego sprawdzenia.

Ten samouczek jest przeznaczony dla osoby edytującej niewielki interfejs przeglądarkowy na Macu. Zbudujesz lokalny przykład HTML, w którym formularz zamyka okno dialogowe dopiero po pomyślnym przejściu symulowanego sprawdzenia etykiety. Ten sam wzorzec obsługi zdarzeń sprawdza się w zwykłych formularzach, które powodują nawigację lub wysyłają żądanie.

Dlaczego asynchroniczny procedura obsługi wysłania nie opóźnia wysyłania formularza?

Przeglądarka nie czeka na obietnicę zwróconą przez procedurę obsługi zdarzenia. Algorytm wysyłania zdarzeń DOM wywołuje procedury obsługi i kontynuuje działanie; algorytm wysyłania formularza w HTML sprawdza, czy zdarzenie submit zostało anulowane przed dalszym działaniem. Dodanie async do procedury obsługi nie wprowadza etapu oczekiwania do tego cyklu życia. Standard DOM: wysyłanie zdarzeń, Standard HTML: algorytm wysyłania formularza

Funkcja async działa synchronicznie do pierwszego await, a następnie kontynuuje działanie asynchronicznie. Dlatego anulowanie musi nastąpić przed pierwszym await, nawet jeśli oczekiwana obietnica jest już spełniona. Późniejsze wywołanie preventDefault() nie cofnie decyzji o wysłaniu formularza, którą przeglądarka już podjęła. MDN: funkcja async

Anulowanie kończy pierwotną próbę; nie wstrzymuje jej, aby wznowić ją później. Po pomyślnej walidacji kod musi jawnie zażądać kolejnego wysłania formularza. Ponadto preventDefault() nie zatrzymuje propagacji zdarzenia, więc inne procedury obsługi nadal mogą je wykryć. MDN: Event.preventDefault()

Nasłuchuj zdarzenia submit formularza. Obejmuje to wysłanie za pomocą przycisku, obsługiwaną niejawną formę wysłania, na przykład naciśnięcie Enter, oraz wywołania requestSubmit(). Procedura obsługi kliknięcia samego przycisku nie obejmuje wszystkich sposobów wysłania formularza. Natywna walidacja ograniczeń zwykle odbywa się jako pierwsza: nieprawidłowe pole wymagane uniemożliwia wysłanie formularza bez wywołania submit. MDN: zdarzenie submit elementu HTMLFormElement

Która metoda zachowuje sposób wysyłania formularza?

Użyj requestSubmit(), gdy po pomyślnym sprawdzeniu formularz ma wrócić do zwykłego cyklu wysyłania. Bezpośrednie wywołanie submit() pomija zarówno walidację ograniczeń, jak i zdarzenie submit. Taki skrót zmienia zachowanie, które asynchroniczna procedura obsługi ma zachować. MDN: HTMLFormElement.submit()

Podejście Co się dzieje Przydatność w tym przypadku
Zwrócenie obietnicy z procedury obsługi Wysyłanie zdarzeń jest kontynuowane bez oczekiwania na obietnicę Nie opóźnia domyślnej akcji
Anulowanie, a następnie wywołanie submit() Wysłanie formularza pomija natywną walidację i procedury obsługi zdarzenia submit Zmienia cykl życia
Anulowanie, a następnie wywołanie requestSubmit(submitter) Natywna walidacja i zdarzenie submit są uruchamiane ponownie Zachowuje zamierzony sposób wysyłania

Zapisz event.submitter przed oczekiwaniem. Przekazanie tego przycisku do requestSubmit() zachowuje jego rolę, w tym nadpisania dotyczące wysyłania oraz wkład jego nazwy i wartości, jeśli ma to zastosowanie. Jeśli nie było przycisku wysyłania, wywołaj requestSubmit() bez argumentu. Przekazany przycisk nadal musi należeć do formularza; nieprawidłowy przycisk lub przycisk należący do innego formularza powoduje wyjątek. MDN: HTMLFormElement.requestSubmit()

Nowa próba wysłania ponownie uruchamia procedurę obsługi. Dlatego poniższy przykład wykorzystuje dwa osobne elementy stanu: pending zapobiega nakładającym się na siebie asynchronicznym sprawdzeniom, a allowNextSubmit zezwala dokładnie na jedno synchroniczne przejście przez procedurę obsługi. Stała flaga „zwalidowano” byłaby zbyt ogólna, bo późniejsze zmiany mogłyby wykorzystać wcześniejszy wynik.

Utwórz kompletny lokalny przykład na Macu

Zapisz poniższy kompletny dokument jako async-form.html w edytorze kodu zapisującym zwykły tekst, a następnie otwórz go w aktualnej przeglądarce na Macu. Nie są potrzebne biblioteki, importy, serwer ani instalacja. Standard wymienia obsługę requestSubmit() w Safari 16 i nowszych wersjach. Standard HTML: interfejsy API formularzy

Przykład wykorzystuje method="dialog", aby domyślna akcja była łatwa do zaobserwowania lokalnie. Pomyślne wysłanie zamyka okno dialogowe i ustawia jego returnValue na podstawie przycisku wysyłania; nie przesyła kontrolek formularza. Przycisk otwierający wywołuje showModal(), aby wyświetlić okno. MDN: element dialog, MDN: HTMLDialogElement.showModal()

Walidator jest celowo symulowany: po krótkim opóźnieniu akceptuje etykiety zawierające co najmniej trzy znaki po usunięciu białych znaków z początku i końca, odrzuca dokładną etykietę taken zapisaną małymi literami i zgłasza wyjątek dla dokładnej etykiety error zapisanej małymi literami. Opóźnienie ilustruje asynchroniczne zakończenie; nie jest pomiarem wydajności.

<!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>

Zachowanie przeglądarki w przykładzie wynika z udokumentowanego cyklu życia. Niewielki test Node.js EventTarget potwierdził różnicę w czasie: synchroniczne anulowanie zmieniło wynik wysyłania zdarzenia, natomiast anulowanie po await nie wpłynęło na ten wynik. Pełny przykład z oknem dialogowym nie został tutaj uruchomiony w przeglądarce na Macu.

Jak blokada zachowuje kolejność i obsługuje zmiany?

Przy pierwszej próbie wysłania procedura obsługi natychmiast anuluje zdarzenie i zapisuje etykietę oraz przycisk, który zainicjował wysłanie. Gdy sprawdzanie jest w toku, kolejne zdarzenie wysłania również jest anulowane, ale nie rozpoczyna następnej walidacji. Dzięki temu seria szybkich prób wysłania nie powoduje nakładania się sprawdzeń.

Po zakończeniu sprawdzania procedura obsługi weryfikuje, czy okno dialogowe jest nadal otwarte i czy etykieta się nie zmieniła. Licznik wersji wykrywa zmiany wprowadzone przez użytkownika, nawet jeśli zmieni on tekst, a potem go przywróci. Porównanie wartości wykrywa również bezpośrednią zmianę wartości, która pozostawia inne brzmienie pola.

Pomyślny wynik ustawia allowNextSubmit bezpośrednio przed wywołaniem requestSubmit(). Ta metoda ponownie uruchamia natywną walidację i ścieżkę zdarzenia submit. Gdy procedura obsługi zostaje wywołana ponownie, zużywa zezwolenie i kończy działanie bez anulowania zdarzenia. Przeglądarka może wtedy wykonać domyślną akcję zamknięcia okna dialogowego. Taka kolejność jest zgodna z sekwencją określoną w standardzie: najpierw walidacja, potem zdarzenie submit. Standard HTML: algorytm wysyłania formularza

Wewnętrzny finally usuwa zezwolenie, nawet jeśli nie wystąpi zdarzenie submit lub metoda zgłosi wyjątek. Zewnętrzny finally usuwa pending po odrzuceniu, wyjątku, nieaktualnym wyniku lub pomownej ponownej próbie. Niepomyślna próba pozostawia więc formularz gotowy do kolejnego sprawdzenia.

Zamknięcie okna dialogowego również zwiększa licznik wersji. Wynik z wcześniejszego otwarcia nie może autoryzować kolejnego. W tym samodzielnym dokumencie te sprawdzenia obejmują dostępne ścieżki interakcji. Jeśli dodasz więcej pól lub skryptów, które je aktualizują, rozszerz obsługę migawki i wersji na każdą wartość, od której zależy walidator.

Sprawdź ścieżki pomyślne, odrzucone i przerwane

Wykonaj te sprawdzenia w lokalnym przykładzie przeglądarkowym. Oczekiwane wyniki wynikają z dostarczonego kodu; nie są to deklarowane wyniki testów przeglądarkowych.

Działanie Oczekiwany wynik
Pozostaw etykietę pustą i wybierz Continue Natywna walidacja wymaganego pola blokuje wysłanie, zanim uruchomi się asynchroniczna procedura obsługi
Wpisz ab i wybierz Continue Okno dialogowe pozostaje otwarte i po sprawdzeniu wyświetla komunikat o odrzuceniu
Wpisz taken Reguła symulacyjna odrzuca tę etykietę
Wpisz sample Okno dialogowe pozostaje otwarte podczas sprawdzania, a potem zamyka się z wartością zwrotną accepted
Wpisz error Pojawia się komunikat o błędzie, a ponowne wysłanie nadal jest możliwe
Wyślij formularz, a następnie edytuj etykietę podczas sprawdzania Wynik zostaje odrzucony i formularz prosi o ponowne wysłanie
Wysyłaj formularz wielokrotnie podczas sprawdzania Tylko pierwsza próba rozpoczyna asynchroniczne sprawdzanie
Wybierz Cancel podczas sprawdzania Okno dialogowe zamyka się bez czekania na oczekujący wynik

Spróbuj też wysłać formularz klawiszem Enter, gdy pole jest aktywne. Sposób niejawnego wysyłania zależy od struktury formularza i konwencji platformy; obsługa submit ma na celu objęcie każdej obsługiwanej interakcji, która faktycznie wywołuje wysłanie formularza. MDN: zdarzenie submit elementu HTMLFormElement

Co się zmienia, gdy formularz wysyła zwykłe żądanie?

W istniejącym formularzu wysyłającym żądanie zachowaj jego prawdziwe action, method, nazwy pól, wartości przycisków i nadpisania dotyczące wysyłania. Zastąp symulowany walidator właściwym sprawdzeniem asynchronicznym. Kod otwierający i zamykający okno dialogowe jest specyficzny dla tej demonstracji; sekwencja anuluj–sprawdź–zażądaj należy do procedury obsługi wysyłania formularza.

Pozostaw pola dostępne na potrzeby ostatecznego wysłania. Wyłączanie kontrolek na czas oczekiwania wymaga ostrożności: wyłączone pola są pomijane, gdy przeglądarka tworzy listę wpisów do wysłania. Przykład zamiast tego pozwala na edycję i odrzuca nieaktualne wyniki walidacji. Standard HTML: tworzenie listy wpisów

Jeśli istnieją inne procedury obsługi zdarzenia submit, będą one widzieć zarówno pierwszą anulowaną próbę, jak i późniejszą próbę. Skoordynuj ich działanie, aby nie wykonywały tej samej pracy dwa razy ani nie zmieniały zwalidowanych wartości podczas dozwolonego przejścia. requestSubmit() również nie gwarantuje zakończenia: inna procedura obsługi może anulować wysłanie, a natywne ograniczenia mogą je zablokować. Zwracaną wartością jest undefined, a nie obietnica zakończenia. MDN: HTMLFormElement.requestSubmit()

Kluczowe założenie jest precyzyjne: anuluj synchronicznie, zwaliduj określony zestaw wartości, odrzuć nieaktualne wyniki i zezwól na jedną nową próbę wysłania formularza przez przeglądarkę. Dzięki temu walidacja asynchroniczna ma jasno określone miejsce w procesie, a cykl życia formularza w przeglądarce zostaje zachowany.

Źródła