Bir form gönderimini geliştirmek için JavaScript fetch
API'sini kullandıysanız, yanlışlıkla yinelenen istek/yarış koşulu hatasına neden olma ihtimaliniz yüksektir. Bugün size bu sorunu anlatacağım ve bundan kaçınmak için önerilerimi anlatacağım.
(İsterseniz video sonunda)
Çok temel bir konuyu ele alalım
<form method="post"> <label for="name">Name</label> <input id="name" name="name" /> <button>Submit</button> </form>
Gönder butonuna bastığımızda tarayıcı tüm sayfayı yenileyecektir.
Gönder düğmesine tıklandıktan sonra tarayıcının nasıl yeniden yüklendiğine dikkat edin.
Sayfa yenileme her zaman kullanıcılarımıza sunmak istediğimiz deneyim değildir; bu nedenle yaygın bir alternatif, sayfa yenilemeyi kullanmaktır.fetch
API'sini kullanarak göndermek için.
Basit bir yaklaşım aşağıdaki örneğe benzeyebilir.
Sayfa (veya bileşen) bağlandıktan sonra form DOM düğümünü alırız, formu kullanarak bir fetch
isteği oluşturan bir olay dinleyicisi eklerizpreventDefault()
yöntemini çağırırız.
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(); }
Şimdi, herhangi bir JavaScript etkin noktası bana GET ve POST hakkında tweet atmaya başlamadan ve gövde metnini talep etmeden öncefetch
isteğini kasıtlı olarak basit tutuyorum çünkü asıl odak nokta bu değil.
Buradaki en önemli konu event.preventDefault()
dur. Bu yöntem, tarayıcının yeni sayfayı yükleme ve formu gönderme gibi varsayılan davranışı gerçekleştirmesini engeller.
Şimdi ekrana bakıp gönder tuşuna basarsak sayfanın yeniden yüklenmediğini görebiliriz ancak ağ sekmemizde HTTP isteğini görürüz.
Tarayıcının tam sayfayı yeniden yüklemediğine dikkat edin.
Ne yazık ki, varsayılan davranışı önlemek için JavaScript kullanarak, aslında varsayılan tarayıcı davranışında olmayan bir hata ortaya çıkardık.
Düz kullandığımızda
Bunu JavaScript örneğiyle karşılaştırırsak tüm isteklerin gönderildiğini ve hiçbirinin iptal edilmeden tamamlandığını görürüz.
Bu bir sorun olabilir, çünkü her istek farklı süre alsa da başlatıldıklarından farklı bir sırayla çözümlenebilir. Bu, bu isteklerin çözümüne işlevsellik eklersek bazı beklenmedik davranışlarla karşılaşabileceğimiz anlamına gelir.
Örnek olarak, her istek için artacak bir değişken (" totalRequestCount
") oluşturabiliriz. handleSubmit
fonksiyonunu her çalıştırdığımızda, mevcut isteği takip etmek için toplam sayıyı artırmanın yanı sıra mevcut sayıyı da yakalayabiliriz (“ thisRequestNumber
”).
Bir fetch
isteği çözümlendiğinde, ona karşılık gelen numarayı konsola kaydedebiliriz.
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(); }
Şimdi, gönder düğmesini birkaç kez kırarsak, konsola farklı sayıların sırasız şekilde yazdırıldığını görebiliriz: 2, 3, 1, 4, 5. Bu ağ hızına bağlıdır, ancak sanırım hepimiz aynı fikirdeyiz. bu ideal değil.
Bir kullanıcının arka arkaya birkaç fetch
isteği tetiklediği ve bu istek tamamlandıktan sonra uygulamanızın sayfayı değişikliklerle güncellediği bir senaryo düşünün. Kullanıcı, isteklerin sıra dışı bir şekilde çözülmesi nedeniyle sonuçta hatalı bilgiler görebilir.
Tarayıcı önceki istekleri iptal ettiğinden ve en son istek tamamlandıktan sonra sayfayı yükleyerek en güncel sürümü yüklediğinden, bu, JavaScript olmayan dünyada bir sorun değildir. Ancak sayfa yenilemeleri o kadar da seksi değil.
JavaScript severler için iyi haber şu ki, her ikisine de sahip olabiliyoruz
Sadece biraz daha ayak işi yapmamız gerekiyor.
fetch
API belgelerine bakarsanız, AbortController
ve fetch
seçeneklerinin signal
özelliğini kullanarak bir getirme işlemini iptal etmenin mümkün olduğunu göreceksiniz. Şunun gibi bir şeye benziyor:
const controller = new AbortController(); fetch(url, { signal: controller.signal });
fetch
isteğine AbortContoller
sinyalini sağlayarak, AbortContoller
abort
yöntemi tetiklendiğinde isteği iptal edebiliriz.
JavaScript konsolunda daha net bir örnek görebilirsiniz. Bir AbortController
oluşturmayı, fetch
isteğini başlatmayı ve ardından hemen abort
yöntemini çalıştırmayı deneyin.
const controller = new AbortController(); fetch('', { signal: controller.signal }); controller.abort()
Hemen konsola yazdırılan bir istisna görmelisiniz. Chromium tarayıcılarında "Yakalanmadı (sözde) DOMException: Kullanıcı bir isteği iptal etti." yazmalıdır. Ağ sekmesini incelerseniz, Durum Metni "(iptal edildi)" ile başarısız bir istek görmelisiniz.
Bunu aklımızda tutarak, formumuzu gönderme işleyicisine bir AbortController
ekleyebiliriz. Mantık şu şekilde olacaktır:
AbortController
kontrol edin. Eğer varsa, iptal edin.
AbortController
oluşturun.
AbortController
öğesini kaldırın.
Bunu yapmanın birkaç yolu var, ancak gönderilen her <form>
DOM düğümü ile ilgili AbortController
arasındaki ilişkileri depolamak için bir WeakMap
kullanacağım. Bir form gönderildiğinde WeakMap
buna göre kontrol edip güncelleyebiliriz.
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); }
Önemli olan, bir iptal denetleyicisini karşılık gelen formuyla ilişkilendirebilmektir. Formun DOM düğümünü WeakMap
anahtarı olarak kullanmak bunu yapmanın kolay bir yoludur.
Bunu yerine getirdiğimizde, fetch
isteğine AbortController
sinyalini ekleyebilir, önceki denetleyicileri iptal edebilir, yenilerini ekleyebilir ve tamamlandığında bunları silebiliriz.
Umarım bunların hepsi mantıklıdır.
Şimdi o formun gönder butonunu defalarca kırarsak, en sonuncusu dışındaki tüm API isteklerinin iptal edildiğini görebiliriz.
Bu, bu HTTP yanıtına yanıt veren herhangi bir işlevin beklediğiniz gibi davranacağı anlamına gelir.
Şimdi, yukarıda sahip olduğumuz aynı sayma ve kayıt mantığını kullanırsak, gönder düğmesini yedi kez kırabiliriz ve konsolda altı istisna ( AbortController
nedeniyle) ve bir "7" günlüğü görürüz.
Tekrar gönderirsek ve isteğin çözülmesi için yeterli süre tanırsak konsolda "8" rakamını görürüz. Ve gönder düğmesine birkaç kez basarsak, istisnaları ve son istek sayısını doğru sırayla görmeye devam edeceğiz.
Bir istek iptal edildiğinde konsolda DOMExceptions'ın görünmesini önlemek için biraz daha mantık eklemek istiyorsanız, fetch
isteğinizin arkasına bir .catch()
bloğu ekleyebilir ve hatanın adının “ AbortError
“ ile eşleşip eşleşmediğini kontrol edebilirsiniz:
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 });
Bu yazının tamamı JavaScript ile geliştirilmiş formlara odaklandı, ancak bir fetch
isteği oluşturduğunuzda AbortController
eklemek muhtemelen iyi bir fikirdir. Zaten API'ye yerleşik olmaması gerçekten çok kötü. Ancak umarım bu size onu dahil etmek için iyi bir yöntem gösterir.
Ayrıca bu yaklaşımın kullanıcının gönder düğmesine defalarca spam göndermesini engellemediğini de belirtmekte fayda var. Düğme hâlâ tıklanabilir durumda ve istek hâlâ etkinleşiyor; yalnızca yanıtlarla ilgilenmek için daha tutarlı bir yol sağlıyor.
Ne yazık ki, bir kullanıcı gönder düğmesini spam olarak gönderirse , bu istekler yine de arka ucunuza gider ve bir sürü gereksiz kaynağın tüketilmesine neden olabilir.
Bazı saf çözümler, bir gönder düğmesini kullanarak gönder düğmesini devre dışı bırakmak olabilir.
Komut dosyasıyla yazılmış istekler yoluyla kötüye kullanımı gidermezler.
Sunucunuza gelen çok fazla istekten kaynaklanan kötüye kullanımı gidermek için muhtemelen bazı ayarlar yapmak isteyebilirsiniz.
Hız sınırlamanın, yinelenen istekler, yarış koşulları ve tutarsız kullanıcı arayüzü güncellemeleri gibi orijinal sorunları çözmediğini de belirtmekte fayda var. İdeal olarak, her iki ucu da kaplamak için her ikisini de kullanmalıyız.
Her neyse, bugünlük söyleyeceklerim bu kadar. Aynı konuyu kapsayan bir video izlemek istiyorsanız bunu izleyin.
Okuduğunuz için çok teşekkür ederim. Bu makaleyi beğendiyseniz lütfen
İlk olarak şu tarihte yayınlandı: