Olay döngüsünü ve geri aramaları anlama

0 Hisse senetleri
0
0
0
0

giriiş

İnternetin ilk zamanlarında, web siteleri genellikle bir HTML sayfasındaki statik verilerden oluşuyordu. Ancak artık web uygulamaları daha etkileşimli ve dinamik hale geldiğinden, API verilerini almak için harici ağ istekleri gibi yoğun işlemler gerçekleştirmek giderek daha gerekli hale geldi. Bu işlemleri JavaScript'te gerçekleştirmek için bir geliştiricinin eşzamansız programlama tekniklerini kullanması gerekir.

JavaScript, işlemleri birbiri ardına işleyen eş zamanlı bir yürütme modeline sahip tek iş parçacıklı bir programlama dili olduğundan, aynı anda yalnızca bir komutu işleyebilir. Ancak, bir API'den veri istemek gibi bir eylem, istenen verinin boyutuna, ağ bağlantısının hızına ve diğer faktörlere bağlı olarak belirsiz bir süre alabilir. API çağrıları eşzamanlı olarak yapılsaydı, tarayıcı, kaydırma veya bir düğmeye tıklama gibi hiçbir kullanıcı girdisini, bu işlem tamamlanana kadar işleyemezdi. Bu, engelleme olarak bilinir.

Engelleme davranışını önlemek için, tarayıcı ortamında JavaScript'in erişebileceği ve eşzamansız olan birçok web API'si bulunur; bu, sıralı olarak değil, diğer işlemlerle paralel olarak yürütülebilecekleri anlamına gelir. Bu, eşzamansız işlem işlenirken kullanıcının tarayıcıyı normal şekilde kullanmaya devam etmesini sağladığı için faydalıdır.

Olay döngüsü

Bu bölüm, JavaScript'in olay döngüsüyle eşzamansız kodu nasıl işlediğini açıklıyor. İlk olarak, olay döngüsünün nasıl çalıştığını gösteriyor ve ardından olay döngüsünün iki öğesini açıklıyor: yığın ve kuyruk.

Eşzamanlı web API'lerinden hiçbirini kullanmayan JavaScript kodu, eşzamanlı olarak, yani sırayla, teker teker yürütülür. Bu, her biri konsola bir sayı yazdıran üç işlevi çağıran şu örnek kodla gösterilmiştir:

// Define three example functions
function first() {
console.log(1)
}

function second() {
console.log(2)
}

function third() {
console.log(3)
}

Bu kodda, console.log() kullanarak sayıları yazdıran üç fonksiyon tanımlıyorsunuz.

Daha sonra fonksiyonlara yapılan çağrıları yazalım:

// Execute the functions
first()
second()
third()

Çıktı, fonksiyonların çağrılma sırasına göre olacaktır: first(), second(), sonra three():

Output
1
2
3

Eşzamansız bir Web API kullanıldığında kurallar daha karmaşık hale gelir. Deneyebileceğiniz yerleşik API'lerden biri, bir zamanlayıcı ayarlayan ve belirli bir süre sonra bir eylem gerçekleştiren setTimeout'tur. setTimeout eşzamansız olmalıdır, aksi takdirde beklerken tüm tarayıcı donar ve bu da kötü bir kullanıcı deneyimine neden olur.

Asenkron bir isteği simüle etmek için ikinci fonksiyona setTimeout ekleyin:

// Define three example functions, but one of them contains asynchronous code
function first() {
console.log(1)
}

function second() {
setTimeout(() => {
console.log(2)
}, 0)
}

function third() {
console.log(3)
}

setTimeout iki argüman alır: eşzamansız olarak çalıştırılacak bir fonksiyon ve bu fonksiyonu çağırmadan önce beklenecek süre. Bu kodda, console.log dosyasını anonim bir fonksiyona koyup setTimeout'a aktarıyorsunuz, ardından fonksiyonu 0 milisaniye sonra çalıştırılacak şekilde ayarlıyorsunuz.

Şimdi fonksiyonları daha önce olduğu gibi çağıralım:

// Execute the functions
first()
second()
third()

setTimeout'u 0 olarak ayarladığınızda, bu üç işlevin çalıştırılmasının sayıları yine de sıralı olarak yazdıracağını düşünebilirsiniz. Ancak eşzamansız olduğu için, işlev sonuncusunda bir kesme ile yazdıracaktır:

Output
1
3
2

Zamanlayıcıyı sıfır saniyeye mi yoksa beş dakikaya mı ayarladığınız önemli değil; eşzamansız kod tarafından çağrılan console.log, en üst düzey eşzamansız işlevlerden sonra çalışacaktır. Bunun nedeni, JavaScript ana bilgisayar ortamının (bu durumda tarayıcı), eşzamanlı veya paralel olayları işlemek için olay döngüsü adı verilen bir kavram kullanmasıdır. JavaScript aynı anda yalnızca bir komut çalıştırabildiğinden, belirli bir komutun ne zaman yürütüldüğünü bilmek için olay döngüsüne ihtiyaç duyar. Olay döngüsü bunu yığınlar ve kuyruklar kavramlarıyla işler.

Yığın

Yığın veya çağrı yığını, o anda yürütülmekte olan fonksiyonun durumunu tutar. Yığın kavramına aşina değilseniz, onu "Son giren, ilk çıkar" (LIFO) özelliklerine sahip bir dizi olarak düşünebilirsiniz; bu, yalnızca yığının en altındaki öğeleri ekleyip çıkarabileceğiniz anlamına gelir. JavaScript, yığındaki geçerli çerçeveyi (veya belirli bir ortamdaki fonksiyon çağrısını) yürütür, ardından onu kaldırır ve bir sonraki çerçeveye geçer.

Yalnızca eşzamanlı kod içeren bir örnek için tarayıcı aşağıdaki sırayla çalışır:

  • Birinci() Yığına ekle, konsola 1 kaydeden first()'i çalıştır, yığından first()'i kaldır.
  • ikinci() Yığına ekle, konsola 2 yazdıran second()'ı çalıştır, stack'ten second()'ı kaldır.
  • üçüncü () Yığına ekle, konsola 3 kaydeden third()'i çalıştır, third()'i yığından kaldır.

setTimout ile ikinci örnek şu şekildedir:

  • Birinci() Yığına ekle, konsola 1 kaydeden first()'i çalıştır, yığından first()'i kaldır.
  • ikinci() Yığına ekle, second()'ı çalıştır.
    • setTimeout()'u yığına ekleyin, zamanlayıcıyı başlatan ve anonim işlevi kuyruğa ekleyen Web API setTimeout()'u çalıştırın, setTimeout()'u yığından kaldırın.
  • ikinci() Yığından çıkarın.
  • Üçüncü () Yığına ekle, konsola 3 kaydeden third()'i çalıştır, third()'i yığından kaldır.
  • Olay döngüsü, bekleyen mesajlar için kuyruğu kontrol eder ve setTimeout()'tan anonim işlevi bulur, işlevi 2'yi konsola kaydeden yığına ekler ve ardından yığından kaldırır.

Asenkron bir Web API olan setTimeout'u kullanarak, bu eğitimde daha sonra ele alınacak olan kuyruk kavramını tanıtır.

Sıra

Kuyruk, aynı zamanda mesaj kuyruğu veya görev kuyruğu olarak da adlandırılır ve işlevler için bir bekleme alanıdır. Çağrı yığını boş olduğunda, olay döngüsü, en eski mesajdan başlayarak kuyrukta bekleyen herhangi bir mesaj olup olmadığını kontrol eder. Bir mesaj bulduğunda, onu yığına ekler ve yığın, mesajda bulunan işlevi çalıştırır.

setTimeout örneğinde, zamanlayıcı 0 saniyeye ayarlandığı için anonim işlev, üst düzey yürütmenin geri kalanından hemen sonra yürütülür. Zamanlayıcının, kodun tam olarak 0 saniyede veya belirli bir zamanda yürütüleceği anlamına gelmediğini, bunun yerine anonim işlevi o süre içinde kuyruğa ekleyeceğini unutmamak önemlidir. Bu kuyruk sistemi, zamanlayıcının süresi dolduktan sonra anonim işlevi doğrudan yığına eklemesi durumunda, o anda yürütülmekte olan işlevi kesintiye uğratacağı ve bunun da beklenmedik ve öngörülemeyen etkilere yol açabileceği için mevcuttur.

Not: Promise'leri işleyen iş kuyruğu veya mikrogörev kuyruğu adı verilen başka bir kuyruk daha vardır. Promise'ler gibi mikrogörevler, setTimeout gibi makrogörevlerden daha yüksek öncelikle yürütülür.

Artık olay döngüsünün kod yürütme sırasını yönetmek için yığın ve kuyruğu nasıl kullandığını bildiğinize göre, bir sonraki adım kodunuzdaki yürütme sırasını nasıl kontrol edeceğinizi bulmaktır. Bunu yapmak için, önce olay döngüsünün eşzamansız kodu doğru şekilde işlemesini sağlamanın temel yolunu, yani geri çağırma işlevlerini öğreneceksiniz.

Geri Arama Fonksiyonları

setTimeout örneğinde, zaman aşımına uğrayan işlev, üst düzey yürütmenin ana bağlamındaki her şeyden sonra yürütülür. Ancak, üçüncü işlev gibi işlevlerden birinin zaman aşımından sonra yürütülmesini sağlamak istiyorsanız, eşzamansız kodlama tekniklerini kullanmanız gerekir. Buradaki zaman aşımı, veri içeren eşzamansız bir API çağrısını temsil edebilir. API çağrısından gelen verilerle çalışmak istiyorsunuz, ancak önce verilerin döndürüldüğünden emin olmanız gerekir.

Bu sorunun temel çözümü geri çağırma fonksiyonlarını kullanmaktır. Geri çağırma fonksiyonlarının özel bir sözdizimi yoktur. Bunlar, başka bir fonksiyona argüman olarak aktarılan bir fonksiyondur. Başka bir fonksiyonu argüman olarak alan bir fonksiyona yüksek düzeyli fonksiyon denir. Bu tanıma göre, herhangi bir fonksiyon argüman olarak aktarıldığında çağrılan bir fonksiyon haline gelebilir. Telefon görüşmeleri doğası gereği eşzamansız değildir, ancak eşzamansız amaçlar için kullanılabilirler.

İşte daha yüksek düzeyli bir fonksiyonun ve bir geri çağırmanın sözdizimi kod örneği:

// A function
function fn() {
console.log('Just a function')
}

// A function that takes another function as an argument
function higherOrderFunction(callback) {
// When you call a function that is passed as an argument, it is referred to as a callback
callback()
}

// Passing a function
higherOrderFunction(fn)

Bu kodda, fn fonksiyonunu tanımlıyorsunuz, bir geri çağırma fonksiyonunu argüman olarak alan aboveOrderFunction fonksiyonunu tanımlıyorsunuz ve fn'yi aboveOrderFunction'a geri çağırma olarak geçiriyorsunuz.

Bu kodun çalıştırılması aşağıdaki sonuçları verecektir:

Output
Just a function

setTimeout ile birinci, ikinci ve üçüncü fonksiyonlara geri dönelim. Şu ana kadar elde ettiklerimiz şunlar:

function first() {
console.log(1)
}

function second() {
setTimeout(() => {
console.log(2)
}, 0)
}

function third() {
console.log(3)
}

Görev, üçüncü fonksiyonun, ikinci fonksiyondaki eşzamansız eylem tamamlanana kadar yürütmeyi her zaman geciktirmesini sağlamaktır. Geri aramalar tam da bu noktada devreye girer. Birinci, ikinci ve üçüncü fonksiyonları yürütmenin en üst düzeyinde yürütmek yerine, üçüncü fonksiyonu ikinci fonksiyona argüman olarak aktarırsınız. İkinci fonksiyon, eşzamansız eylem tamamlandıktan sonra geri aramayı yürütür.

Geri arama ile uygulanan üç fonksiyon şunlardır:

// Define three functions
function first() {
console.log(1)
}

function second(callback) {
setTimeout(() => {
console.log(2)

// Execute the callback function
callback()
}, 0)
}

function third() {
console.log(3)
}

Şimdi birinci ve ikinciyi yürütün, ardından üçüncüyü ikinciye argüman olarak geçirin:

first()
second(third)

Bu kod bloğunu çalıştırdığınızda aşağıdaki çıktıyı alacaksınız:

Output
1
2
3

İlk olarak 1 yazdırılır ve zamanlayıcı süresi dolduktan sonra (bu durumda sıfır saniyedir, ancak bunu herhangi bir değere değiştirebilirsiniz) 2 yazdırılır ve ardından 3. Asenkron Web API (setTimeout) tamamlanana kadar çalışılır.

Buradaki önemli nokta, geri çağırma işlevlerinin eşzamansız olmamasıdır. setTimeout, eşzamansız görevleri işlemekten sorumlu eşzamansız bir Web API'sidir. Geri çağırmalar, yalnızca eşzamansız bir görev tamamlandığında bildirim almanızı ve bu görevin başarısını veya başarısızlığını yönetmenizi sağlar.

Artık geri aramaları eşzamansız görevleri gerçekleştirmek için nasıl kullanacağınızı öğrendiğinize göre, bir sonraki bölümde çok fazla çağrıyı iç içe geçirmenin ve bir "felaket piramidi" oluşturmanın yol açtığı sorunlar açıklanmaktadır.

İç içe geçmiş geri çağırma ve felaket piramidi

Geri çağırma fonksiyonları, bir fonksiyonun başka bir fonksiyon tamamlanıp veriyle dönene kadar gecikmesini sağlamanın etkili bir yoludur. Ancak, geri çağırmaların iç içe geçmiş yapısı nedeniyle, birbirine bağlı birçok ardışık eşzamansız isteğiniz varsa kod karmaşık hale gelebilir. Bu, ilk JavaScript geliştiricileri için büyük bir hayal kırıklığıydı ve sonuç olarak, iç içe geçmiş çağrılar içeren kod genellikle "işkence piramidi" veya "geri çağırma cehennemi" olarak anılır.

İşte iç içe geçmiş geri aramaların bir gösterimi:

function pyramidOfDoom() {
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 500)
}, 2000)
}, 1000)
}

Bu kodda, her yeni setTimeout, daha üst düzey bir fonksiyonun içine yerleştirilerek, giderek daha derin çağrılardan oluşan bir piramit oluşturulur. Bu kodu çalıştırmak aşağıdakileri üretecektir:

Output
1
2
3

Pratikte, gerçek dünyadaki eşzamansız kodlarda bu çok daha karmaşık hale gelebilir. Muhtemelen eşzamansız koddaki hataları işlemeniz ve ardından her yanıttan bir sonraki isteğe veri aktarmanız gerekecektir. Bunu geri aramalarla yapmak, kodunuzun okunmasını ve bakımını zorlaştırır.

İşte oynayabileceğiniz daha gerçekçi bir "kıyamet piramidi"nin uygulanabilir bir örneği:

// Example asynchronous function
function asynchronousRequest(args, callback) {
// Throw an error if no arguments are passed
if (!args) {
return callback(new Error('Whoa! Something went wrong.'))
} else {
return setTimeout(
// Just adding in a random number so it seems like the contrived asynchronous function
// returned different data
() => callback(null, {body: args + ' ' + Math.floor(Math.random() * 10)}),
500,
)
}
}
// Nested asynchronous requests
function callbackHell() {
asynchronousRequest('First', function first(error, response) {
if (error) {
console.log(error)
return
}
console.log(response.body)
asynchronousRequest('Second', function second(error, response) {
if (error) {
console.log(error)
return
}
console.log(response.body)
asynchronousRequest(null, function third(error, response) {
if (error) {
console.log(error)
return
}
console.log(response.body)
})
})
})
}
// Execute 
callbackHell()

Bu kodda, her bir fonksiyon için olası bir yanıt ve olası bir hata hesaba katmalısınız, bu da callbackHell fonksiyonunu görsel olarak kafa karıştırıcı hale getirir.

Bu kodu çalıştırdığınızda aşağıdaki sonucu elde edeceksiniz:

Output
First 9
Second 3
Error: Whoa! Something went wrong.
at asynchronousRequest (<anonymous>:4:21)
at second (<anonymous>:29:7)
at <anonymous>:9:13

Sonuç

Eşzamansız kodu yönetmenin bu yöntemi zordur. Bu nedenle, ES6'da vaatler kavramı ortaya çıkmıştır. Bir sonraki bölümün odak noktası budur.

Bir yorum
Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Ayrıca Şunları da Beğenebilirsiniz