JS’de çoklu HTTP isteklerini doğru şekilde kullanmak

Orçun TUNA
4 min readAug 29, 2022

--

Selamlar, Medium’da uzun süredir yazı yazmıyordum, bu yazı vesilesiyle geri dönmüş olalım. Genelde reviewlerde ve internetteki kod örneklerinde yanlış kullanıma çokça rastladığım bir konuya değinmek istiyorum. Yazı başlığını tam olarak doğru seçebildim mi emin değilim ama basit bir örnek ile başlıkta anlatmak istediğim şeyi açıklayabilirim diye düşünüyorum.

Senaryomuz: Bir sayfanın yüklenmesi için 3 farklı yerden veri çektiğimizi düşünelim. Bu 3 veriyi şu şekilde tanımlayalım: Kategoriler, Yazarlar ve Yazılar. Bu veriler API üzerinden gelecek ve bu yüzden tahmin edemeyeceğimiz bir bekletme süresine sahip olacak. Eğer bu verilerin yüklenmesi birbirine bağlı değilse bu verileri şu şekilde çekebiliriz:

Not: Kod örneklerini verirken hayali getCategories, getAuthors ve getPosts fonksiyonları varmış gibi davranacağız ve bu fonksiyonların tümü Promise dönüyor olacak. Ayrıca await kullanılan tüm kod blokları async bir fonksiyon içerisinde gibi değerlendirilecek.

getCategories(categories => setCategories(categories))
getAuthors(authors => setAuthors(authors))
getPosts(posts => setPosts(posts))
// bu satırda categories, authors ve posts verilerinin hiç birine sahip değiliz

JavaScript bu kodları sırayla yukarıdan aşağıya doğru cevabın gelmesini beklemeden çalıştıracak ve bu 3 veri birbirinden bağımsız olarak yüklenmiş olacak. Bu birbirinden bağımsız verilere ihtiyacımız olduğu senaryoda gayet işlevsel bir kullanım gibi duruyor fakat bizim bu yazıdaki konumuz 3 istekten gelen verilerin birbirine ihtiyacı olduğu durum. Yani 3 veriye de aynı anda ihtiyacımız var ve bunları işleyeceğiz. O zaman ne yapardık? Hemen aklımıza gelen ilk yöntemi uygulayalım.

const categories = await getCategories()
const authors = await getAuthors()
const posts = await getPosts()
// bu satırda categories, authors ve posts verilerine sahibiz

Üstteki kod bloğunda yorum satırı bıraktığımız yerde tüm verilere sahibiz, istediğimiz veri işlemeyi yapıp lazım olduğu şekilde kullanabiliriz. Eğer sizin de aklınıza gelen yöntem veya şu ana kadar benzer senaryolarda uyguladığınız çözüm buysa maalesef yanlıştı. Hadi küçük bir hesap yapalım ve sonrasında size yukarıdaki kodun nasıl çalıştığınız gösteren bir kod bloğu daha paylaşayım.

Yukarıdaki 3 verinin her birinin API’dan ne kadar sürede yüklendiğini rasgele bir şekilde belirleyelim ve hesaplamalarımızda bu sayıları kullanalım:

  • Categories: 1.4 saniye
  • Authors: 1.0 saniye
  • Posts: 1.6 saniye

Peki yukarıdaki kod JavaScript tarafında nasıl bir mantıkla çalıştı? Tam olarak aşağıdaki gibi:

getCategories().then(categories => {
getAuthors().then(authors => {
getPosts().then(posts => {
// burada tüm verilere sahibiz
})
})
})

Yani şu mantıkla ilerledi: birinci isteğin bitmesini bekledi, sonra ikinci isteğe geçti, ikinci istek de bitince son olarak üçüncü isteği de attı ve tüm verilere sahip olduğumuz noktaya gelene kadar adımlar birbirini beklediği için belirlediğimiz sürelere üzerinden toplam (1.4 + 1.0 + 1.6) 4 saniye beklemiş olduk. Bu süreyi unutmayın bir sonraki adımdaki süre ile karşılaştıracağız.

Şimdi gelelim doğru yönteme. Sıralı istek gönderme yerine verilerin birbirine ihtiyacı olmadığı yönteme benzer şekilde tüm istekleri aynı anda başlatır ve sonuçların hepsinin geldiği durumu takip edersek olumlu yönde büyük bir süre farkı yakalayabiliriz. İsteklerin aynı anda başlatılıp hepsinin bitmesini kendi yazdığımız fonksiyonlar ile de sağlayabiliriz ama JavaScript bize bunu hali hazırda sunduğu için buna ihtiyacımız yok. İlacımız Promise.all() olacak. Promise.all, içerisine gönderilen promise’lerin hepsini eş zamanlı olarak işler ve sonuçlarını bize geri döndürür. Hemen bir örnek yapalım ve sonrasında performans farkına göz atalım.

const values = await Promise.all([getCategories(), getAuthors(), getPosts()])
// burada values[0], values[1], values[2]'e sahibiz

Buradaki püf noktalardan biri ise; bize array içerisinde geri döndürülen sonuçlar, parametrelere gönderdiğimiz sıra ile dönecektir. Bu da demek oluyorki “values” değişkeni yerine şu şekilde direkt olarak sonuçları istediğimiz değişkenlere atayabiliriz.

const [categories, authors, posts] = await Promise.all([getCategories(), getAuthors(), getPosts()])
// burada categories, authors ve posts'a sahibiz

Sadece await ile kullanmayıp Promise.all([...params]).then(values) şeklinde de kullanım yapabilirdik. Kod bloğunda daha derli toplu durması için örnekte await ile işlem yaptım. Peki bu yaptığımız şey bize ne kadar süre kazandırdı? İsteklerimiz aynı anda çıktığı için burada bizim sonuçlara ulaşana kadarki toplam süremiz en uzun isteğimizin süresi olacak. Elimizdeki isteklerin süreleri 1.4, 1.0 ve 1.6 olduğuna göre en uzun süre olan 1.6 saniyede tüm verilere ulaşmış olduk. Bu süreleri en başta söylediğim gibi tahmini olarak verdik ve tabiki kullanılan internetin düşük performanslı olduğu durumda 3 isteğin aynı anda çıkması verilerin dönüş hızını etkileyebilir ama büyük bir fark olmayacaktır o yüzden ~1.6 saniye olarak değerlendirelim.

Sıralı isteklerin zaman çizelgesinin görselini yukarıya eklemiştim, bir de eş zamanlı gönderilen isteklerimizin zaman çizelgesine bakalım:

Sıralı isteklerde tüm sonuçlara 4 saniyede ulaşmıştık, bu durumda aradaki fark ise 2.4 saniye oldu. Gerçekten epey gözle görülür bir fark ve bu durum kullanıcı deneyimini kötü yönde etkileyecekti.

Sonradan ekleme: Promise.all() ile yaptığımız 3 isteği aynı anda başlatma isteğinde eğer bir veya birden çok promise hata döndürürse, Promise.all eğer try catch bloğu içerisindeyse catch’e düşecektir. Yine aynı şekilde then, catch, finally şeklinde kullanırsanız then yerine catch’e düşecektir. Hata yönetimi senaryolarını değerlendirmek için göz ardı edilmemesi gereken bir nokta olduğu sonradan bu paragrafı ekliyorum.

Umarım faydalı ve anlaşılır bir yazı olmuştur. Elimden geldiği kadar örnek ve görseller hazırladım. Öneri/eleştirileriniz için yorumları kullanabilirsiniz. Kendinize iyi bakın görüşmek üzere..

Bu yazıda kullanılan kaynaklar:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

--

--