JavaScript, eşzamansız (engellenmeyen) ve tek iş parçacıklı bir programlama dilidir; bu, aynı anda yalnızca bir işlemin çalıştırılabileceği anlamına gelir.
Programlama dillerinde geri arama cehennemi genellikle eşzamansız çağrılarla kod yazmanın etkisiz bir yolunu ifade eder. Kıyamet Piramidi olarak da bilinir.
JavaScript'teki geri arama cehennemi, aşırı miktarda iç içe geçmiş geri arama işlevinin yürütüldüğü bir durum olarak adlandırılır. Kodun okunabilirliğini ve bakımını azaltır. Geri arama cehennemi durumu genellikle birden fazla API isteğinde bulunmak veya karmaşık bağımlılıklara sahip olayları işlemek gibi eşzamansız istek işlemleriyle uğraşırken ortaya çıkar.
JavaScript'teki geri arama cehennemini daha iyi anlamak için öncelikle JavaScript'teki geri aramaları ve olay döngülerini anlayın.
JavaScript'te geri aramalar
JavaScript her şeyi dizeler, diziler ve işlevler gibi bir nesne olarak değerlendirir. Dolayısıyla geri çağırma kavramı, işlevi başka bir işleve argüman olarak aktarmamıza olanak tanır. Önce geri çağırma işlevi yürütmeyi tamamlayacak ve ana işlev daha sonra yürütülecektir.
Geri çağırma işlevleri eşzamansız olarak yürütülür ve eşzamansız görevin tamamlanmasını beklemeden kodun çalışmaya devam etmesine olanak tanır. Birden fazla eşzamansız görev birleştirildiğinde ve her görev bir önceki göreve bağlı olduğunda kod yapısı karmaşık hale gelir.
Geri aramaların kullanımını ve önemini anlayalım. Bir örnek olarak, üç parametre, bir dize ve iki sayı alan bir fonksiyonumuzun olduğunu varsayalım. Birden fazla koşula sahip dize metnine dayalı bazı çıktılar istiyoruz.
arp-a komutu
Aşağıdaki örneği göz önünde bulundurun:
function expectedResult(action, x, y){ if(action === 'add'){ return x+y }else if(action === 'subtract'){ return x-y } } console.log(expectedResult('add',20,10)) console.log(expectedResult('subtract',30,10))
Çıktı:
30 20
Yukarıdaki kod düzgün çalışacaktır ancak kodu ölçeklenebilir hale getirmek için daha fazla görev eklememiz gerekiyor. Koşullu ifadelerin sayısı da artmaya devam edecek ve bu da optimize edilmesi ve okunması gereken karmaşık bir kod yapısına yol açacaktır.
Yani kodu daha iyi bir şekilde şu şekilde yeniden yazabiliriz:
function add(x,y){ return x+y } function subtract(x,y){ return x-y } function expectedResult(callBack, x, y){ return callBack(x,y) } console.log(expectedResult(add, 20, 10)) console.log(expectedResult(subtract, 30, 10))
Çıktı:
30 20
Yine de çıktı aynı olacaktır. Ancak yukarıdaki örnekte, ayrı işlev gövdesini tanımladık ve işlevi bir geri çağırma işlevi olarak beklenenResult işlevine aktardık. Bu nedenle, beklenen sonuçların işlevselliğini farklı bir işlemle başka bir işleyen gövde oluşturup onu geri çağırma işlevi olarak kullanabilmek için genişletmek istiyorsak, kodun anlaşılmasını kolaylaştıracak ve okunabilirliğini geliştirecektir.
Desteklenen JavaScript özelliklerinde geri aramaların başka farklı örnekleri de mevcuttur. Birkaç yaygın örnek, Olay dinleyicileri ve harita, azaltma, filtre vb. gibi dizi işlevleridir.
Bunu daha iyi anlayabilmek için JavaScript'in değere göre geçişi ve referansa göre geçişi anlamamız gerekir.
JavaScript, ilkel ve ilkel olmayan iki tür veri türünü destekler. İlkel veri türleri tanımsız, null, string ve boolean olup değiştirilemez veya karşılaştırmalı olarak değişmez diyebiliriz; İlkel olmayan veri türleri, değiştirilebilen veya değiştirilebilen diziler, işlevler ve nesnelerdir.
Referansla geçirme, bir fonksiyonun argüman olarak alınabilmesi gibi, bir varlığın referans adresini iletir. Dolayısıyla, eğer bu fonksiyonun içindeki değer değiştirilirse, fonksiyonun dışında mevcut olan orijinal değer de değişecektir.
Karşılaştırmalı olarak, değere göre geçiş kavramı, işlev gövdesinin dışında bulunan orijinal değerini değiştirmez. Bunun yerine hafızasını kullanarak değeri iki farklı konuma kopyalayacaktır. JavaScript tüm nesneleri referanslarına göre tanımladı.
JavaScript'te addEventListener, tıklama, fareyle üzerine gelme ve fareyi kaldırma gibi olayları dinler ve ikinci argümanı, olay tetiklendiğinde yürütülecek bir işlev olarak alır. Bu fonksiyon referans kavramına göre kullanılır ve parantezsiz olarak aktarılır.
Aşağıdaki örneği düşünün; bu örnekte, geri çağırma işlevi olarak addEventListener'a argüman olarak bir greet işlevini aktardık. Click olayı tetiklendiğinde çağrılacaktır:
Test.html:
Javascript Callback Example <h3>Javascript Callback</h3> Click Here to Console const button = document.getElementById('btn'); const greet=()=>{ console.log('Hello, How are you?') } button.addEventListener('click', greet)
Çıktı:
Yukarıdaki örnekte, geri çağırma işlevi olarak addEventListener'a argüman olarak bir greet işlevi ilettik. Click olayı tetiklendiğinde çağrılacaktır.
Benzer şekilde filtre de geri çağırma fonksiyonunun bir örneğidir. Bir diziyi yinelemek için bir filtre kullanırsak, dizi verilerini işlemek için argüman olarak başka bir geri çağırma işlevi gerekir. Aşağıdaki örneği inceleyin; bu örnekte dizideki 5'ten büyük sayıyı yazdırmak için büyük fonksiyonunu kullanıyoruz. Filter metodunda geri çağırma fonksiyonu olarak isGreater fonksiyonunu kullanıyoruz.
Java'da özyineleme
const arr = [3,10,6,7] const isGreater = num => num > 5 console.log(arr.filter(isGreater))
Çıktı:
[ 10, 6, 7 ]
Yukarıdaki örnek, filtre yönteminde büyük fonksiyonun geri çağırma fonksiyonu olarak kullanıldığını göstermektedir.
JavaScript'teki Geri Aramaları, Olay döngülerini daha iyi anlamak için eşzamanlı ve eşzamansız JavaScript'i tartışalım:
Eşzamanlı JavaScript
Senkron programlama dilinin özelliklerinin neler olduğunu anlayalım. Senkron programlama aşağıdaki özelliklere sahiptir:
Yürütmenin Engellenmesi: Senkron programlama dili, bloklama yürütme tekniğini destekler; bu, mevcut ifadelerin yürütüleceği sonraki ifadelerin yürütülmesini bloke ettiği anlamına gelir. Böylece, ifadelerin öngörülebilir ve deterministik yürütülmesini sağlar.
Sıralı Akış: Eşzamanlı programlama, sıralı yürütme akışını destekler; bu, her ifadenin birbiri ardına sıralı bir şekilde yürütüldüğü anlamına gelir. Dil programı bir sonraki ifadeye geçmeden önce bir ifadenin tamamlanmasını bekler.
Basitlik: Çoğu zaman Senkron programlamanın anlaşılması kolay olduğu düşünülür çünkü yürütme akışının sırasını tahmin edebiliriz. Genellikle doğrusaldır ve tahmin edilmesi kolaydır. Küçük uygulamaların bu dillerde geliştirilmesi iyidir çünkü kritik işlem sırasını idare edebilir.
Doğrudan Hata İşleme: Senkron programlama dilinde hata yönetimi çok kolaydır. Bir ifade yürütülürken bir hata meydana gelirse, bir hata verir ve program onu yakalayabilir.
Özetle, senkronize programlamanın iki temel özelliği vardır; yani, aynı anda tek bir görev yürütülür ve sonraki görevler dizisi yalnızca mevcut görev tamamlandığında ele alınacaktır. Böylece sıralı bir kod yürütülmesini takip eder.
Bir ifade yürütülürken programlamanın bu davranışı, her işin bir önceki işin tamamlanmasını beklemesi gerektiğinden, dil bir blok kod durumu yaratır.
Ancak insanlar JavaScript hakkında konuştuğunda, bunun eşzamanlı mı yoksa eşzamansız mı olduğu her zaman kafa karıştırıcı bir cevap olmuştur.
Yukarıda tartışılan örneklerde, bir işlevi filtre işlevinde geri çağırma olarak kullandığımızda, eşzamanlı olarak yürütüldü. Bu nedenle buna senkronize yürütme denir. Filtre işlevi, daha büyük işlevin yürütülmesini bitirmesini beklemek zorundadır.
Bu nedenle, geri çağırma işlevi, çağrıldığı ana işlevin yürütülmesini engellediğinden geri aramaları engelleme olarak da adlandırılır.
Öncelikle, JavaScript tek iş parçacıklı eşzamanlı ve doğası gereği engelleyici olarak kabul edilir. Ancak birkaç yaklaşım kullanarak farklı senaryolara göre eşzamansız çalışmasını sağlayabiliriz.
Şimdi eşzamansız JavaScript'i anlayalım.
npm önbelleğini temizleme
Eşzamansız JavaScript
Eşzamansız programlama dili, uygulamanın performansını artırmaya odaklanır. Geri aramalar bu tür senaryolarda kullanılabilir. JavaScript'in eşzamansız davranışını aşağıdaki örnekle analiz edebiliriz:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000)
Yukarıdaki örnekte, setTimeout işlevi argüman olarak bir geri çağırmayı ve milisaniye cinsinden zamanı alır. Geri arama, belirtilen süreden sonra çağrılır (burada 1s). Özetle, fonksiyon yürütülmesi için 1 saniye bekleyecektir. Şimdi aşağıdaki koda bir göz atın:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000) console.log('first') console.log('Second')
Çıktı:
first Second greet after 1 second
Yukarıdaki koddan, zamanlayıcı geçerken ilk olarak setTimeout'tan sonraki günlük mesajları yürütülecektir. Dolayısıyla 1 saniye sonra tebrik mesajı ve 1 saniye sonra da karşılama mesajı çıkıyor.
JavaScript'te setTimeout eşzamansız bir işlevdir. setTimeout fonksiyonunu her çağırdığımızda, belirtilen gecikmeden sonra yürütülecek bir geri çağırma fonksiyonunu (bu durumda greet) kaydeder. Ancak sonraki kodun yürütülmesini engellemez.
Yukarıdaki örnekte, günlük mesajları hemen yürütülen senkronize ifadelerdir. setTimeout işlevine bağlı değildirler. Bu nedenle, setTimeout'ta belirtilen gecikmeyi beklemeden ilgili mesajlarını yürütür ve konsola kaydederler.
Bu arada, JavaScript'teki olay döngüsü eşzamansız görevleri yönetir. Bu durumda belirlenen gecikme süresinin (1 saniye) geçmesini bekler ve bu süre geçtikten sonra geri çağırma fonksiyonunu (greet) alıp çalıştırır.
Böylece setTimeout fonksiyonundan sonraki diğer kod arka planda çalışırken çalışıyordu. Bu davranış, eşzamansız işlemin tamamlanmasını beklerken JavaScript'in diğer görevleri gerçekleştirmesine olanak tanır.
JavaScript'teki eşzamansız olayları işlemek için çağrı yığınını ve geri arama kuyruğunu anlamamız gerekir.
Aşağıdaki görüntüyü göz önünde bulundurun:
Yukarıdaki görselden tipik bir JavaScript motorunun bir yığın hafızadan ve bir çağrı yığınından oluştuğunu görüyoruz. Çağrı yığını, yığına itildiğinde beklemeden tüm kodu çalıştırır.
Yığın belleği, ihtiyaç duyulduğunda çalışma zamanında nesneler ve işlevler için belleğin tahsis edilmesinden sorumludur.
Artık tarayıcı motorlarımız DOM, setTimeout, console, fetch vb. gibi çeşitli web API'lerinden oluşuyor ve motor bu API'lere global window nesnesini kullanarak erişebilir. Bir sonraki adımda, bazı olay döngüleri, geri arama kuyruğundaki işlev isteklerini seçen ve bunları yığına iten ağ geçidi denetleyicisi rolünü oynar. setTimeout gibi bu işlevler belirli bir bekleme süresi gerektirir.
Şimdi örneğimize, setTimeout fonksiyonuna dönelim; fonksiyonla karşılaşıldığında zamanlayıcı geri arama kuyruğuna kaydedilir. Bundan sonra, kodun geri kalanı çağrı yığınına gönderilir ve işlev zamanlayıcı sınırına ulaştığında, süresi dolduğunda ve geri arama kuyruğu, belirtilen mantığa sahip olan ve zaman aşımı işlevinde kayıtlı olan geri çağırma işlevini ittiğinde yürütülür. . Böylece belirlenen süreden sonra yürütülecektir.
Geri Arama Cehennemi Senaryoları
Şimdi geri aramalar, senkronize, asenkron ve geri arama cehennemiyle ilgili diğer konuları tartıştık. JavaScript'te geri arama cehenneminin ne olduğunu anlayalım.
Birden fazla geri aramanın iç içe geçtiği durum, kod şeklinin 'kıyamet piramidi' olarak da adlandırılan bir piramite benzemesi nedeniyle geri arama cehennemi olarak bilinir.
Geri arama cehennemi kodun anlaşılmasını ve korunmasını zorlaştırır. Bu durumu çoğunlukla node JS’de çalışırken görebiliriz. Örneğin aşağıdaki örneği inceleyin:
getArticlesData(20, (articles) => { console.log('article lists', articles); getUserData(article.username, (name) => { console.log(name); getAddress(name, (item) => { console.log(item); //This goes on and on... } })
Yukarıdaki örnekte getUserData, makale listesine bağlı olan veya makalenin içindeki getArticles yanıtının çıkarılması gereken bir kullanıcı adı alır. getAddress'in de getUserData'nın yanıtına bağlı olan benzer bir bağımlılığı vardır. Bu duruma geri arama cehennemi denir.
Geri arama cehenneminin iç işleyişi aşağıdaki örnekle anlaşılabilir:
A görevini gerçekleştirmemiz gerektiğini anlayalım. Bir görevi gerçekleştirmek için B görevinden bazı verilere ihtiyacımız var. Benzer şekilde; Birbirine bağımlı olan ve eşzamansız olarak yürütülen farklı görevlerimiz var. Böylece bir dizi geri çağırma işlevi oluşturur.
JavaScript'teki Promises'ı ve bunların eşzamansız işlemleri nasıl oluşturduklarını anlayarak iç içe geri aramalar yazmaktan kaçınmamızı sağlayalım.
JavaScript vaatleri
JavaScript'te sözler ES6'da tanıtıldı. Sözdizimsel kaplamaya sahip bir nesnedir. Eşzamansız davranışı nedeniyle, eşzamansız işlemler için geri aramaların yazılmasını önlemenin alternatif bir yoludur. Günümüzde fetch() gibi Web API'leri, sunucudaki verilere erişmenin etkili bir yolunu sağlayan ümit verici kullanılarak uygulanmaktadır. Ayrıca kodun okunabilirliğini de geliştirdi ve iç içe geçmiş geri aramaların yazılmasını önlemenin bir yoludur.
Gerçek hayatta verilen sözler, iki veya daha fazla kişi arasındaki güveni ve belirli bir şeyin mutlaka gerçekleşeceğine dair güvenceyi ifade eder. JavaScript'te Promise, gelecekte (gerektiğinde) tek bir değer üretilmesini sağlayan bir nesnedir. JavaScript'teki Promise, eşzamansız işlemleri yönetmek ve ele almak için kullanılır.
Promise, eşzamansız işlemlerin tamamlanmasını veya başarısızlığını ve çıktısını sağlayan ve temsil eden bir nesne döndürür. Kesin çıktıyı bilmeden bir değerin proxy'sidir. Eşzamansız eylemlerin nihai bir başarı değeri veya başarısızlık nedeni sağlaması yararlıdır. Böylece asenkron yöntemler, senkronize bir yöntem gibi değerleri döndürür.
Genellikle vaatler aşağıdaki üç duruma sahiptir:
- Yerine getirildi: Yerine getirildi durumu, uygulanan bir eylemin başarıyla çözümlendiği veya tamamlandığı durumdur.
- Beklemede: Beklemede durumu, isteğin devam ettiği ve uygulanan eylemin çözümlenmediği veya reddedilmediği ve hala başlangıç durumunda olduğu zamandır.
- Reddedildi: Reddedilen durum, uygulanan eylemin reddedilmesi ve istenen işlemin başarısız olmasına neden olmasıdır. Reddedilme nedeni, sunucunun kapalı olması da dahil olmak üzere herhangi bir şey olabilir.
Vaatlerin sözdizimi:
let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data });
Aşağıda vaatlerin yazılmasına bir örnek verilmiştir:
Bu bir söz yazma örneğidir.
function getArticleData(id) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Fetching data....'); resolve({ id: id, name: 'derik' }); }, 5000); }); } getArticleData('10').then(res=> console.log(res))
Yukarıdaki örnekte, sunucudan istekte bulunmak için verilen sözleri nasıl verimli bir şekilde kullanabileceğimizi görebiliriz. Yukarıdaki kodda kod okunabilirliğinin geri çağırmalara göre arttığını gözlemleyebiliriz. Promises, başarı veya başarısızlık durumunda operasyon durumunu yönetmemize olanak tanıyan .then() ve .catch() gibi yöntemler sağlar. Vaatlerin farklı durumları için durumları belirtebiliriz.
JavaScript'te Eşzamansız/Bekliyor
İç içe geçmiş geri aramaların kullanımını önlemenin başka bir yoludur. Async/Await, vaatleri çok daha verimli kullanmamızı sağlıyor. .then() veya .catch() yöntem zincirlemesini kullanmaktan kaçınabiliriz. Bu yöntemler aynı zamanda geri çağırma işlevlerine de bağlıdır.
performans testi
Async/Await, uygulamanın performansını artırmak için Promise ile tam olarak kullanılabilir. Vaatleri kendi içinde çözümledi ve sonucunu sağladı. Ayrıca yine () veya catch() yöntemlerinden daha okunabilirdir.
Async/Await işlevini normal geri çağırma işlevleriyle kullanamayız. Bunu kullanmak için, işlev anahtar sözcüğünün önüne bir async anahtar sözcüğü yazarak bir işlevi eşzamansız hale getirmeliyiz. Ancak dahili olarak zincirlemeyi de kullanır.
Aşağıda Async/Await'in bir örneği verilmiştir:
async function displayData() { try { const articleData = await getArticle(10); const placeData = await getPlaces(article.name); const cityData = await getCity(place) console.log(city); } catch (err) { console.log('Error: ', err.message); } } displayData();
Async/Await'i kullanmak için işlevin async anahtar sözcüğüyle belirtilmesi ve işlevin içine wait anahtar sözcüğünün yazılması gerekir. Zaman uyumsuz, Söz çözülene veya reddedilene kadar yürütülmesini durduracaktır. Vaat yerine getirildiğinde yeniden başlatılacak. Çözüldükten sonra, bekleme ifadesinin değeri onu tutan değişkende saklanacaktır.
Özet:
Özetle, vaatleri ve eşzamansız/beklemeyi kullanarak iç içe geri aramalardan kaçınabiliriz. Bunların dışında yorum yazmak, kodu ayrı bileşenlere bölmek gibi diğer yaklaşımları da takip edebiliriz. Ancak günümüzde geliştiriciler async/await kullanımını tercih ediyor.
Çözüm:
JavaScript'teki geri arama cehennemi, aşırı miktarda iç içe geçmiş geri arama işlevinin yürütüldüğü bir durum olarak adlandırılır. Kodun okunabilirliğini ve bakımını azaltır. Geri arama cehennemi durumu genellikle birden fazla API isteğinde bulunmak veya karmaşık bağımlılıklara sahip olayları işlemek gibi eşzamansız istek işlemleriyle uğraşırken ortaya çıkar.
JavaScript'teki geri arama cehennemini daha iyi anlamak için.
JavaScript her şeyi dizeler, diziler ve işlevler gibi bir nesne olarak değerlendirir. Dolayısıyla geri çağırma kavramı, işlevi başka bir işleve argüman olarak aktarmamıza olanak tanır. Önce geri çağırma işlevi yürütmeyi tamamlayacak ve ana işlev daha sonra yürütülecektir.
Geri çağırma işlevleri eşzamansız olarak yürütülür ve eşzamansız görevin tamamlanmasını beklemeden kodun çalışmaya devam etmesine olanak tanır.