JavaScirpt Event Loop Nedir?

Mehmet Demirel
6 min readSep 12, 2023

Herkese merhaba,

Bu gün sizlerle birlikte JavaScript’in olay döngüsüne dayalı çalışma zamanı modelinin (runtime model based on an event loop) ne olduğunu öğreneceğiz. En temelden en üst düzeye sizinle birlikte öğrenmeye başlayalım.

Öncelikle bilmeyenler için JavaScript’ten bahsedelim.

JavaScript, single-threaded (tek iş parçacıklı) bir dildir. Yani, birim zamanda sadece tek bir iş yapabilir. JavaScript, tüm işlemleri single-threaded bir şekilde yürütür; ancak akıllı veri yapıları (smart data structures) kullanarak, bize çoklu iş parçacığı (multi-threaded) gibi bir izlenim verir (illusion of multi-threading). Bu da JavaScript’in uzun süren işlemleri bekletmeden diğer işlemleri de devam ettirebilmesini sağlar.

JavaScript’in single-threaded olması, şu anlama gelir: JavaScript aynı anda sadece bir işi halledebilir. Bu, JavaScript’in işlemleri sırayla gerçekleştirdiği anlamına gelir. Eğer bir işlem uzun sürerse, diğer işlemler beklemek zorunda kalır. Örneğin, bir web tarayıcısı, bir sayfadaki JavaScript kodunu çalıştırırken, bu kodun işlemlerini tek bir “thread” veya iş parçacığında gerçekleştirir. Eğer bu kodun içinde uzun süren bir işlem varsa (örneğin, büyük bir dosyanın yüklenmesi), tarayıcı diğer işlemleri bu işlem tamamlanana kadar bekletir. Bu durum, sayfanın beklenmedik şekilde donması veya yavaşlaması anlamına gelebilir.

Asenkron programlama (Asynchronous programming), uzun işlemler nedeniyle ortaya çıkan kullanıcı deneyimi sorunlarını çözmek için kullanılan bir tekniktir. Bu teknikle, uzun süren işlemler aynı anda gerçekleştirilebilir ve diğer işlemler bu işlemi beklemek zorunda kalmaz. Bunun yerine, işlem tamamlandığında bir Callback Function (Geri Arama İşlevi/Çağrısı) çağrılır ve sonuç ile birlikte devam edilir. Bu sayede, uzun süren işlemler aynı anda gerçekleştirilebilir ve diğer işlemler bu işlemi beklemek zorunda kalmaz. Örneğin, bir dosyanın indirilmesi işlemi asenkron olarak gerçekleştirilebilir, böylece tarayıcı diğer işlemleri aksatmadan devam edebilir. Asenkron programlama, JavaScript’in daha verimli ve hızlı çalışmasını sağlar. Bu, web sayfalarının daha hızlı yanıt vermesini ve daha akıcı bir kullanıcı deneyimi sunmasını sağlar.

JavaScript’in ne olduğunu artık biraz kavradık veya hatırladık gibi görünüyor. Şimdi birlikte Event Loop’un ne olduğun anlamak için adım adım kullandığı mekanizmaları açıklayarak ilerleyelim.

Memory Heap (Bellek Yığını)

Bir programın çalışma süresi boyunca dinamik olarak oluşturulan ve yönetilen verilerin depolandığı alandır. Bu alana genellikle nesneler, diziler, fonksiyonlar gibi veri yapıları ve değerler yerleştirilir. Aynı zamanda, programın çalışması sırasında geçici veriler de burada saklanır. Bellek yığını, genellikle iki temel bölümden oluşur: Heap ve Stack. Heap, dinamik verilerin saklandığı alandır ve özellikle new anahtar kelimesi ile oluşturulan nesneler burada bulunur. Stack ise fonksiyon çağrıları ve yerel değişkenlerin saklandığı alandır; her bir fonksiyonun çağrıldığı sırada bir "çağrı kaydı" oluşturulur ve bu kayıtlar yığına eklenir. Bellek yığını, programın çalışması için ayrılan RAM'in bir bölümüdür ve program çalıştıkça dinamik olarak büyür veya küçülür.

Call Stack (Çağrı Yığını)

LIFO (last-in-first-out — son giren ilk çıkar) prensibi ile çalışma zamanında Function Calls (İşlev Çağrıları) takibinde kullanılan bir mekanizmadır. Bu, en son eklenen öğenin en önce çıkarılacağı anlamına gelir. Bir programın çalışma zamanında işlevlerin çalışma sırasını takip etmek için kullanılır. Her işlev çağrıldığında bir Call Record (Çağrı Kaydı) oluşturulur ve bu kayıt yığına eklenir. Bu kayıtlar, işlevlerin ne zaman çağrıldığını ve hangi işlevin şu anda çalıştığını izlemek için kullanılır.

Call Stack, genellikle iki ana işlevi yerine getirir:

  1. Fonksiyon Çağrıları: Bir işlev çağrıldığında, bu işlevin argümanları (args) ve yerel değişkenleri ile birlikte bir çağrı kaydı oluşturulur. Bu kayıt yığına eklenir ve işlev çalışmaya başlar.
  2. İşlev Tamamlanmaları: Bir işlevin çalışması tamamlandığında, çağrı kaydı yığından çıkarılır. Eğer bir işlev başka bir işlevi çağırıyorsa, çağrı kaydı yığına eklenir ve bu işlev çalışmaya başlar.

Bu süreç, işlevlerin iç içe geçmiş olabileceği durumlarda çalışır. Örneğin, bir işlev içinde başka bir işlev çağrıldığında, ikinci işlevin çağrı kaydı ilk işlemin üzerine eklenir. Bu işlemler tamamlandığında, çağrı kayıtları sırasıyla yığından çıkarılır.

Özetle, Call Stack, işlev çağrılarını ve işlev tamamlanmalarını takip eden bir mekanizmadır. Bu, programın hangi işlevin şu anda çalıştığını ve işlevlerin ne zaman çağrıldığını anlamamıza yardımcı olur.

Callback Queue (Task Queue)

FIFO (first-in-first-out — ilk giren ilk çıkar) prensibine dayanan asenkron programlama dillerinde kullanılan bir mekanizmadır. Bu kuyruğa, asenkron olarak çalıştırılması gereken Call Stack fonksiyonları eklenir. Daha sonra bu fonksiyonlar, Event Loop boş bir Call Stack bulduğunda Call Stack’e eklenerek çalıştırılırlar. JavaScript’in asenkron çalışma modelinin temelini oluşturur. Özellikle tarayıcılar, kullanıcı etkileşimleri, ağ istekleri veya zamanlayıcılar gibi işlemleri asenkron olarak yönetirken Callback Queue kullanır. Bu sayede, tarayıcı çalışmasını kesmeden diğer işlemlere devam edebilir ve Callback Queue’deki fonksiyonları sırayla çalıştırarak istenilen işlemleri gerçekleştirir.

Web APIs

Tarayıcıların ve diğer çevresel ortamların, web sayfalarının tarayıcı dışındaki dünyayla iletişim kurmasını sağlayan bir dizi önceden tanımlanmış fonksiyon ve metodlardan oluşur. Web APIs, tarayıcıların dış dünyayla etkileşime geçmesini sağlayan bir dizi programlama arayüzüdür. DOM manipülasyonundan, HTTP isteklerine, zamanlayıcılara ve daha fazlasına kadar birçok farklı işlevi içerirler. Bu API’lar, tarayıcı tarafından sunulur ve JavaScript ile kullanılırlar.

Event Loop

JavaScript’in Runtime (çalışma zamanı)’ının temel bir parçasıdır. Bu mekanizma, asenkron programlamanın temelini oluşturur ve sürekli olarak çalışan bir döngüdür.

Event Loop’un ana görevi, program akışını yönetmek ve asenkron işlemleri kontrol etmektir. Bu görevi yerine getirirken, Call Stack ve Callback Queue gibi iki kritik mekanizmayla işbirliği yapar.

Örneğin, bir asenkron işlem tamamlandığında, ilgili Callback Function Callback Queue’e eklenir. Event Loop, bu sırada Call Stack boşsa, bu fonksiyonu hemen Call Stack’e ekler. Bu sayede, asenkron işlemin sonucu doğrudan işlenir ve programın akışı kesintiye uğramadan devam eder.

Bu mekanizma, özellikle ağ istekleri, dosya okuma işlemleri gibi uzun süren işlemler için kritiktir. Bu tür işlemler asenkron olarak gerçekleşir ve Event Loop, bu süreçleri etkili bir şekilde yönetir.

Sonuç olarak, Event Loop, JavaScript’in asenkron doğasını yöneten temel bir mekanizmadır.

Gelin beraber bunu hayatımızdan herhangi bir olay ile senaryo haline getirelim ve daha iyi kavrayalım.

Senaryo

Bir anda canınız kahve içmek istiyor ve kendinizi sokağa atıyorsunuz. Kendinizi self-servis hizmet veren tatlı bir kafenin önünde buluyorsunuz ve içeri adım atıyorsunuz. Kasaya yaklaştığınızda önünüzde iki kişi olduğunu fark ediyor ve beklemeye başlıyorsunuz.

İlk sırada bekleyen kişi, kahvesinin nasıl olması gerektiğini kasadaki elemana anlatıyor. Kasadaki eleman, kahvenin hazır olduğunda yan taraftan alabileceğini belirterek kahve makinesine siparişi giriyor.

Bu sırada eleman, diğer (2nci) müşteriye dönerek siparişini alıyor. Kasadaki eleman, bu bilgileri alıp tekrar kahve makinesine yöneliyor. O sırada, ilk müşterinin siparişi hazır ve müşteriye teslim ediliyor.

Sonrasında, 2. müşterinin siparişi kahve makinesine giriliyor ve tekrar kasaya dönülüyor. Şimdi sıra bizde. Siparişimiz alınıyor ve aynı süreç tekrarlanıyor. Kahvemizi alıp, rasgele gözümüze hoş gelen bir masa bulup oturuyoruz. Oturduğumuz anda, bu süreçleri bir şeylere benzettiğimizi farkediyoruz ve makaledeki bilgiler aklımıza geliyor. Makalede geçen bilgilerin hepsi zihnimize dolanıyor ve düşünmeye başlıyoruz.

Bu sırada, kafenin içindeki hareketliliği gözlemliyoruz. İnsanlar sırayla kasaya gidip siparişlerini veriyor, kahvelerini alıp keyifle içiyorlar. Bu düzenli akış, tam da Event Loop’un çalışma prensibini hatırlatıyor.

Event Loop, sürekli olarak programın akışını kontrol eder. Sırayla işlemleri yönlendirir, bekler ve sonuçları işler. İşte şu an yaşadığımız bu an, sanki bir Event Loop’un içindeki döngünün bir parçası gibi ilerliyor. Herkes sırasıyla işlemlerini gerçekleştiriyor ve programın akışı kesintisiz devam ediyor.

Bu arada, kahvemizin sıcaklığı bizi sakinleştiriyor. Makalede’ki bilgilerle kendi düşüncelerimizi ve gördüklerimizi harmanlıyoruz. İşte burada, Event Loop’un çalışma prensibiyle, her şey birbirine özdeşleşiyor. Her işlem, bir öncekinin sonuçlarıyla şekilleniyor ve programın akışı sürekli bir döngü halinde devam ediyor.

Sonuç olarak, Event Loop, sürekli olarak çalışarak Call Stack’in durumunu kontrol eder. Callback Queue’deki işlemleri Call Stack’e alarak çalıştırır. Bu sayede, asenkron işlemler arka planda çalışabilir ve programın akıcı bir şekilde çalışır.

Gelin birlikte görsel olarak ele alalım ve tamamen kavrayarak bu konu hakkında makalemizi sonlandıralım. Hazırlamış olduğum kodu sizlerle paylaşmak istiyorum.

/* Müşteri nesnelerinin bulunduğu dizi. Her bir müşterinin id,
isim ve kahve türü bilgileri bulunuyor. */
const customers = [{
id: 1,
name: 'Deniz',
coffeeType: 'Latte',
},
{
id: 2,
name: 'Derya',
coffeeType: 'Espresso',
},
{
id: 3,
name: 'Mehmet',
coffeeType: 'Turkish Coffee',
},
];

// Müşterinin kahvesinin hazır olduğunu belirten mesajı yazdıran fonksiyon.
function makeCoffee(customer) {
console.log(`${customer.name}'s ${customer.coffeeType} is ready.`);
};

// Her bir müşterinin siparişini işleyen fonksiyon.
function orderCoffee(customerIndex) {
// Temel durum: Tüm müşteriler işlendiğinde, 3 saniye sonra bir mesaj yazdır.
if (customerIndex >= customers.length) {
setTimeout(() => {
console.log('All orders are received.');
}, 3000);
return;
}

// Diziden mevcut müşteriyi al.
const customer = customers[customerIndex];

// Siparişin alındığına dair bir mesaj yazdır.
console.log(`Order received from ${customer.name}.`);

/* Siparişin hazırlanmasını simüle etmek için
iç içe geçmiş setTimeout'ler kullanılıyor. */
setTimeout(() => {
console.log(`${customer.name}'s order is being prepared...`);

/* Kahvenin yapılmasını simüle etmek için
iç içe geçmiş setTimeout kullanılıyor. */
setTimeout(() => {
makeCoffee(customer);

// Müşterinin kahvesini almasını simüle etmek için 1 saniye bekleniyor.
setTimeout(() => {
console.log(`${customer.name} is taking his/her coffee.`);
}, 1000);
}, 2000);
}, 3000);

// Bir sonraki siparişin işlenmesi için 4 saniye sonra plan yapılıyor.
setTimeout(() => {
orderCoffee(customerIndex + 1);
}, 4000);
};

// İlk müşteri ile başlayarak siparişleri işlemeye başla (index 0).
orderCoffee(0);
Bu linke tıklayarak yukarıda bulunan arayüze sizde gidebilirisiniz.

Buraya kadar okuduğunuz için teşekkür ederim. Olabildiğince bir çok konuya değinmek istedim ve umarım sizin ilginizi çekmiştir. Bir sonraki yazımda görüşmek dileğiyle :).

Bu konuda mutlaka okumanız gereken Transkript ve izlemeniz gereken videosu.

--

--