Panggil event.preventDefault() sebelum await pertama, jalankan validasi asinkron, lalu gunakan form.requestSubmit() untuk memulai percobaan pengiriman baru. Pengaman sekali pakai memungkinkan percobaan kedua tersebut berlanjut tanpa mengulangi pemeriksaan asinkron.
Tutorial ini ditujukan bagi seseorang yang mengedit antarmuka browser kecil di Mac. Anda akan membuat contoh HTML lokal yang formulirnya menutup dialog hanya setelah pemeriksaan label sintetis berhasil. Pola penanganan peristiwa yang sama berlaku untuk formulir biasa yang melakukan navigasi atau mengirim permintaan.
Mengapa penangan pengiriman asinkron tidak menunda pengiriman?
Browser tidak menunggu promise yang dikembalikan oleh pemroses peristiwa. Algoritme pengiriman peristiwa DOM memanggil pemroses lalu melanjutkan; algoritme pengiriman formulir HTML memeriksa apakah peristiwa submit dibatalkan sebelum melanjutkan. Menambahkan async ke pemroses tidak menyisipkan langkah menunggu ke dalam siklus tersebut. Standar DOM: pengiriman peristiwa, Standar HTML: algoritme pengiriman formulir
Fungsi asinkron berjalan secara sinkron hingga await pertamanya, lalu berlanjut secara asinkron. Karena itu, pembatalan harus dilakukan sebelum await pertama, bahkan ketika promise yang ditunggu sudah terpenuhi. Memanggil preventDefault() setelahnya tidak dapat membatalkan keputusan pengiriman yang sudah dibuat browser. MDN: fungsi async
Pembatalan mengakhiri percobaan awal; pembatalan tidak menangguhkannya untuk dilanjutkan nanti. Setelah validasi berhasil, kode Anda harus secara eksplisit meminta pengiriman baru. Selain itu, preventDefault() membiarkan propagasi peristiwa tetap berlangsung, sehingga pemroses lain masih dapat mengamati peristiwa tersebut. MDN: Event.preventDefault()
Dengarkan peristiwa submit pada formulir. Ini mencakup pengiriman melalui tombol, pengiriman implisit yang didukung seperti menekan Enter, serta pemanggilan requestSubmit(). Penangan klik khusus tombol tidak mencakup semua cara pengiriman. Validasi batasan bawaan biasanya berjalan lebih dahulu: kolom wajib yang tidak valid mencegah pengiriman tanpa memicu submit. MDN: peristiwa submit HTMLFormElement
Metode mana yang mempertahankan perilaku pengiriman formulir?
Gunakan requestSubmit() jika pemeriksaan yang berhasil seharusnya kembali ke siklus hidup formulir normal. Memanggil submit() secara langsung melewati validasi batasan dan peristiwa submit. Jalan pintas itu mengubah kontrak yang seharusnya dipertahankan oleh penangan asinkron Anda. MDN: HTMLFormElement.submit()
| Pendekatan | Yang terjadi | Kesesuaian untuk kasus ini |
|---|---|---|
| Mengembalikan promise dari pemroses | Pengiriman peristiwa berlanjut tanpa menunggunya | Tidak menunda aksi default |
Membatalkan, lalu memanggil submit() |
Pengiriman melewati validasi bawaan dan pemroses submit | Mengubah siklus hidup |
Membatalkan, lalu memanggil requestSubmit(submitter) |
Validasi bawaan dan peristiwa submit dijalankan kembali | Mempertahankan jalur pengiriman yang dimaksud |
Simpan event.submitter sebelum menunggu. Meneruskan tombol tersebut ke requestSubmit() mempertahankan perannya, termasuk penggantian perilaku pengiriman dan kontribusi nama/nilainya jika berlaku. Jika tidak ada tombol pengirim, panggil requestSubmit() tanpa argumen. Tombol yang diberikan harus tetap menjadi bagian dari formulir; tombol yang tidak valid atau tombol milik formulir lain akan menimbulkan pengecualian. MDN: HTMLFormElement.requestSubmit()
Pengiriman baru akan memicu pemroses Anda lagi. Karena itu, contoh di bawah menggunakan dua bagian status yang terpisah: pending mencegah pemeriksaan asinkron berjalan bersamaan, sedangkan allowNextSubmit mengizinkan tepat satu kali melewati pemroses secara sinkron. Penanda “sudah divalidasi” yang permanen akan terlalu longgar karena perubahan berikutnya dapat menggunakan kembali hasil sebelumnya.
Buat contoh lokal lengkap di Mac Anda
Simpan dokumen lengkap berikut sebagai async-form.html di editor kode teks biasa, lalu buka dengan browser terkini di Mac Anda. Tidak diperlukan pustaka, impor, server, atau instalasi. Standar tersebut mencantumkan dukungan requestSubmit() mulai Safari 16. Standar HTML: API formulir
Contoh ini menggunakan method="dialog" agar aksi default mudah diamati secara lokal. Pengiriman yang berhasil menutup dialog yang membungkusnya dan menetapkan returnValue dari tombol kirim; contoh ini tidak mengirimkan kontrol formulir. Tombol pembuka memanggil showModal() untuk menampilkannya. MDN: elemen dialog, MDN: HTMLDialogElement.showModal()
Validatornya sengaja dibuat sintetis: setelah jeda singkat, validator menerima label dengan setidaknya tiga karakter setelah spasi di awal dan akhir dihapus, menolak label huruf kecil persis taken, dan melempar pengecualian untuk label huruf kecil persis error. Jeda tersebut menggambarkan penyelesaian asinkron; ini bukan pengukuran kinerja.
<!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>
Perilaku browser pada contoh ini didasarkan pada siklus hidup yang terdokumentasi. Pemeriksaan kecil dengan Node.js EventTarget mengonfirmasi perbedaan waktu: pembatalan sinkron mengubah hasil pengiriman, sedangkan pembatalan setelah await terlambat untuk mengubah hasil tersebut. Contoh dialog lengkap ini belum dijalankan di browser Mac di sini.
Bagaimana pengaman mempertahankan urutan dan menangani perubahan?
Pada pengiriman pertama, pemroses langsung membatalkan dan menyimpan label serta tombol yang memulai pengiriman. Selama pemeriksaan berlangsung, peristiwa submit lain juga dibatalkan, tetapi tidak memulai validasi lain. Ini mencegah rangkaian pengiriman cepat menimbulkan pemeriksaan yang berjalan bersamaan.
Saat pemeriksaan selesai, pemroses memastikan dialog masih terbuka dan label belum berubah. Penghitung revisi mendeteksi edit pengguna, bahkan jika pengguna mengubah teks lalu mengembalikannya. Perbandingan nilai juga menangkap perubahan nilai langsung yang membuat teks di kolom berbeda.
Hasil yang lolos menetapkan allowNextSubmit tepat sebelum memanggil requestSubmit(). Metode tersebut menjalankan kembali jalur validasi bawaan dan peristiwa submit. Saat pemroses dipanggil kembali, pemroses menggunakan izin itu dan kembali tanpa membatalkan. Browser kemudian dapat menjalankan aksi default untuk menutup dialog. Urutan ini mengikuti urutan standar yang menjalankan validasi sebelum peristiwa submit. Standar HTML: algoritme pengiriman formulir
Bagian dalam finally menghapus izin meskipun tidak ada peristiwa submit atau metode tersebut melempar pengecualian. Bagian luar finally menghapus pending setelah penolakan, pengecualian, hasil kedaluwarsa, atau percobaan ulang yang berhasil. Dengan demikian, percobaan yang tidak berhasil tetap memungkinkan formulir diperiksa lagi.
Menutup dialog juga menambah nilai revisi. Hasil dari pembukaan sebelumnya tidak dapat mengizinkan pengiriman pada pembukaan berikutnya. Untuk dokumen mandiri ini, pemeriksaan tersebut mencakup jalur interaksi yang tersedia. Jika Anda menambahkan kolom lain atau skrip yang memperbaruinya, perluas penanganan snapshot dan revisi agar mencakup setiap nilai yang menjadi dasar validator.
Uji jalur keberhasilan, penolakan, dan gangguan
Jalankan pemeriksaan berikut pada contoh browser lokal. Hasil yang diharapkan didasarkan pada kode yang disertakan; hasil tersebut bukan klaim hasil pengujian browser.
| Tindakan | Hasil yang diharapkan |
|---|---|
Biarkan label kosong lalu pilih Lanjutkan |
Validasi kolom wajib bawaan memblokir pengiriman sebelum penangan asinkron dijalankan |
Masukkan ab lalu pilih Lanjutkan |
Dialog tetap terbuka dan menampilkan pesan penolakan setelah pemeriksaan |
Masukkan taken |
Aturan sintetis menolaknya |
Masukkan sample |
Dialog tetap terbuka selama pemeriksaan, lalu tertutup dengan nilai pengembalian accepted |
Masukkan error |
Pesan kegagalan muncul, dan pengiriman lain tetap dapat dilakukan |
| Kirim label, lalu edit saat pemeriksaan berlangsung | Hasil dibuang dan formulir meminta pengiriman ulang |
| Kirim berulang kali saat pemeriksaan berlangsung | Hanya percobaan pertama yang memulai pemeriksaan asinkron |
Pilih Batal saat pemeriksaan berlangsung |
Dialog tertutup tanpa menunggu hasil yang masih tertunda |
Coba juga mengirim dengan menekan Enter saat fokus berada pada kolom input. Pengiriman implisit bergantung pada struktur formulir dan kebiasaan platform; alasan menangani submit adalah untuk mencakup interaksi apa pun yang didukung dan benar-benar memicu pengiriman formulir. MDN: peristiwa submit HTMLFormElement
Apa yang berubah jika formulir mengirim permintaan biasa?
Untuk formulir yang sudah mengirim permintaan, pertahankan action dan method yang sebenarnya, nama kolom, nilai tombol, serta penggantian perilaku pengirimannya. Ganti validator sintetis dengan pemeriksaan asinkron Anda yang sesungguhnya. Kode untuk membuka dan menutup dialog khusus untuk demonstrasi ini; urutan pembatalan–validasi–permintaan tetap berada di penangan pengiriman formulir.
Biarkan kolom tetap tersedia untuk pengiriman akhir. Menonaktifkan kontrol selama menunggu perlu dilakukan dengan hati-hati: kolom yang dinonaktifkan tidak disertakan saat browser menyusun daftar entri pengiriman. Contoh ini membiarkan pengeditan tetap tersedia dan menolak hasil validasi yang kedaluwarsa. Standar HTML: menyusun daftar entri
Jika ada pemroses submit lain, pemroses tersebut akan mengamati percobaan pertama yang dibatalkan maupun percobaan berikutnya. Koordinasikan semuanya agar tidak melakukan pekerjaan yang sama dua kali atau mengubah nilai yang telah divalidasi selama percobaan yang diizinkan. requestSubmit() juga tidak menjamin penyelesaian: pemroses lain dapat membatalkannya, dan batasan bawaan dapat memblokirnya. Nilai yang dikembalikannya adalah undefined, bukan promise penyelesaian. MDN: HTMLFormElement.requestSubmit()
Kontrak utamanya jelas: batalkan secara sinkron, validasi sekumpulan nilai yang telah ditentukan, buang hasil yang sudah kedaluwarsa, dan izinkan satu percobaan pengiriman bawaan yang baru. Dengan demikian, validasi asinkron mendapat tempat yang jelas dalam alur kerja sambil mempertahankan siklus hidup formulir browser.
Sumber
Standar DOM — WHATWG
Standar HTML: Elemen form — WHATWG
Standar HTML: Menyusun daftar entri — WHATWG
fungsi async — MDN Web Docs
Event: metode preventDefault() — MDN Web Docs
HTMLFormElement: peristiwa submit — MDN Web Docs
HTMLFormElement: metode submit() — MDN Web Docs
HTMLFormElement: metode requestSubmit() — MDN Web Docs
Elemen Dialog — MDN Web Docs
HTMLDialogElement: metode showModal() — MDN Web Docs