Synchronous programlamada işlemler sırayla gerçekleşir. Başka bir deyişle bir işlem bitmeden diğerine geçiş yapılmaz. Aktif olan devam ederken sıradaki işlem bloklanır.
1 2 3 4 5 6 |
console.log("Hello,"); const syncExample = () => { console.log("How are you?"); }; syncExample(); console.log("World."); |
Bildiğiniz gibi Javascript bir single-threaded programlama dilidir. Yani kodu satır satır okur ve sırayla işleme sokar. Bu nedenle yukarıda verilen örneğin outputu şu şekildedir.
1 2 3 |
Hello, How are you? World. |
Asynchronous Programlama Nedir?
1 2 3 4 5 6 7 8 9 |
console.log("Hello,"); const asyncExample = () => { setTimeout(() => { console.log("How are you?"); }, 3000); }; asyncExample(); console.log("World."); |
1 2 3 |
Hello, World. How are you? |
Callbacks, Promise ve Async-Await
Bir örnek üzerinden bu yöntemleri, aralarındaki farkları ve kullanım kolaylıklarını inceleyelim.
1 2 3 4 5 |
function printString(string) { setTimeout(() => { console.log(string); }, Math.floor(Math.random() * 100) + 1); } |
Diyelimki random zamana bağlı olarak string yazdıran bir fonksiyonumuz var.
Bu fonksiyonu kullanarak A, B, C harflerini sırayla yazdıralım.
1 2 3 4 5 6 |
function printAll() { printString("A"); printString("B"); printString("C"); } printAll(); |
Her ne kadar sırayla çağırılmış olsalar da printAll fonksiyonu her çağırıldığında A, B, C harfeleri farklı sırayla loglanır. Daha önceden de açıkladığım gibi printString fonksiyonu asynchronous çalışır. A, B, C için her fonksiyon sırayla çalıştırılır ancak setTimeOut fonksiyonundan dolayı farklı zamanlarda gercekleşirler. Bu da A, B, C harflerinin farklı sırayla loglanmasına sebep olur.
Peki bir fonsiyonu art arda kullanmak istiyorsak ve sırayla gercekleşmesini bekliyorsak ne yapmalıyız? Bu sorunun cevabına geçmeden önce de buna neden ihtiyaç duyduğumuzu da belirtmek istiyorum.
Bu tarz işlemlere bir yere istek atarken ve o istekten dönen cevaplara göre kodun devamını getiriyorsak ihtiyaç duyabiliriz. Çünkü kodu sırayla yazsak bile istek atan fonksiyon satırı okunup isteğin cevabına göre işlemi gerçekleştirecek olan fonksiyon satırına geçildiğinde eğer istek bir cevap dönmediyse undefined hatası ile karşılaşırız. Bu hata ile karşılaşmak istemiyorsak da Promise in resolve olduğu yere istekten gelen cevap ile işlem yapan fonksiyonu çağırabiliriz. Aynı zamanda bu isteğin dönmesini beklerken çalışan kodun bulunduğu satırdan sonrası için çalışmayı durdurur. Bu tarz hataların önüne geçebilmek için de calbackler,promiseler ve async-await kullanılır.
Callbacks
Callback fonksiyonları bağımsız bir değişken olarak başka bir fonksiyona geçirilen ve ilk fonksiyonun işlemi tamamlandıktan sonra diger fonksiyonun işlevini başlatan fonksiyonlardır. Callbackler genellikle asynchronous işlemler tamamlandıktan sonra kod yürütülmesine devam etmek için kullanılır.
Gelin bir örnekle duruma göz atalım.
1 2 3 4 5 6 |
function addString(previous, current, callback){ setTimeout(() => { callback((previous + ' ' + current)) },Math.floor(Math.random() * 100) + 1 ) } |
Şimdi de addAll fonksiyonunu kullanarak A,B, C harflerini yazdıralım.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function addAll(){ addString('', 'A', result => { addString(result, 'B', result => { addString(result, 'C', result => { console.log(result) }) }) }) } addAll() //Output: A B C |
Gördüğünüz gibi bu sefer A,B, C harflerini sırayla yazdırmayı başardık lakin kodumuz oldukça karışık gözüküyor. Zaten callbakleri nerede ve ne zaman kullanacağımıza bu nedenle özen göstermeliyiz. Çünkü fonksiyon içinde fonksiyonları kullanmaya başladıkça kodu okumak gittikçe zorlaşacak ve zamanla “Callback Hell” adı verilen problem ortaya çıkacak. Ayrıca Callbacklerin sebep olduğu bazı hatalar da vardır. Bu hatalardan bazıları şunlardır;
- Callback’in beklenenden erken çağrılması,
- Callback’in hiç çağrılmaması,
- Callback’in beklenilenden az veya çok çağrılması,
- Gerekli parametreleri doğru bir şekilde alamaması,
- Hataların kontrolünde zayıflık
Peki bu hataları yaşamamak için ve işlevlerimi yerine getirmek için hangi yolu izlemeliyiz? Bu sorunun cevabı için gelin beraber bir de promiselere göz atalım.
Promise
Promiseler benim gibi Javascript’e yeni başlayan yazılımcılar için büyük ihtimalle en çekimser yaklaşılan konulardan biridir. Peki nedir bu promiseler?
Promiseler, ‘Callback Hell’ durumundan kaçınmak için ve beklenmeyen durumla karşılaşıldığında hata kontrolünün daha rahat yapılabilmesi için geliştirilmiş ES6 olarak bilinen ECMAScript 2015 ile bize sunulan asynchronous bir yapıdır.
Bir promise 3 durumdan oluşur;
- Pending: Bu başlangıç aşamasıdır. Bu aşamada bir şey gerçekleşmez. Bu aşama için şöyle düşünebiliriz, müşteri sana bir sipariş verecektir. Ama henüz bir şey sipariş etmemiştir.
- Resolved: Bu aşama ise işlemin sonuçlandığı ve başarılı olduğu aşamadır. Yani müşteri siparişini almış ve memnun kalmıştır.
- Rejected: Bu aşama ise hata ile sonuçlanan aşamadır. İstenen sipariş gelmemiş ve müşteri restoranı terk etmiştir.
Şimdi gelin beraber addString fonksiyonumuzu promise kullanarak yazalım.
1 2 3 4 5 6 7 |
function addString(previous, current) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(previous + " " + current); }, Math.floor(Math.random() * 100) + 1); }); } |
Gördüğünüz gibi callback kullanmak yerine tüm fonksiyonu bir promise içerisine koyduk. Bu promise sonucunda fonksiyonumuz ya resolve olacak ya da reject. Ayrıca promiselerde eğer işlem başarılı olursa then( ), başarısız olursa ve hata durumu olursa catch( ) fonksiyonlarının içine girer. Bu yapıya ise “Promise Chain” denir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function addAll() { addString("", "A") .then((result) => { return addString(result, "B"); }) .then((result) => { return addString(result, "C"); }) .then((result) => { console.log(result); }) .catch((error) => console.log(error)); } addAll(); //Output: A B C |
Gördüğünüz gibi A,B, C harflerini sırayla yazdırmayı başardık. Kod yapısına baktığımızda ise ilk fonksiyonun diğer fonksiyonun sonucunu return ettiğini ve sonucun bir sonraki fonsiyona gönderildiğini görürüz.
Diğer kısma geçmeden önce promise yapısı ile ilgili promise.all( ) ve promise.race( ), promise.allSettled( ) ve promise.any( ) özelliklerinden de bahsetmek istiyorum.
- Promise.all( ), birden fazla promise tek bir then( ) ve catch( ) ile yazılabiliyor. Promise’lerden biri reject( ) olursa direk catch( )’e girer. Tüm Promise başarıyla tamamlanmasını bekler.
- Promise.race( ), ise promiselerden en önce hangi promise tamamlanırsa onun sonucunu alır.
- Promise.allSettled( ), tüm Promise başarılı, başarısız işletimleri bitince sonuçlarını status leri ile birlikte geriye döner.
- Promise.any( ), bu yöntem, yerine getiren ilk promisi döndürmek için kullanışlıdır. Bir promise yerine getirildikten sonra kısa devre yapar, bu nedenle bir resolve olmuş bir promise bulduktan sonra diğer promiselerin tamamlanmasını beklemez.
Async / Await
Async/Await yapısı asynchronous işlemleri daha anlaşılması kolay bir hale getiren ECMAScript 2017 ile kullanıma sunulan promise tabanlı bir Javascript özelliğidir.
Anlaşılması kolay derken neyden bahsettiğimi açıklayayım. Şöyle ki biz geliştiriciler olarak, çoğunlukla synchrounous kod yazmaya alışkınız, yani birbiri ardına komut dizileri yazarız. Çünkü bu tarz yazım şeklinin okunması ve anlaşılması çok daha kolaydır. Callbackler ve promiseler döngüsellikleri nedeniyle bizi bu açıdan biraz yorar. Async-await tam olarak o anda yardımımıza koşar.
Şimdi yeniden addString ve addAll() fonksiyonumuza bakalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function addString(previous, current) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(previous + " " + current); }, Math.floor(Math.random() * 100) + 1); }); } async function addAll() { let toPrint = await addString("", "A"); toPrint = await addString(toPrint, "B"); toPrint = await addString(toPrint, "C"); console.log(toPrint); } addAll(); //Output: A B C |
Gördüğünüz gibi A,B ve C harflerini sırayla yazdırmayı başardık.
Ayrıca addAll() fonksiyonu içinde geçen ’’async” sözcüğü JavaScript’in async / await sözdizimini kullandığımızı bilmesini sağlar ve Await kullanmak istiyorsanız bu sözcüğü kullanmanız şarttır. Aksi taktirde hata alırsınız.
Async-Await özelliği istek atarken ve o istekten dönen cevaplara göre işlem yapıyorsak sık kullanılan bir yapıdır. Bu nedenle konuyu da daha iyi anlamamız için son bir örnek daha vermek istiyorum.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function sendRequest(id, prev = "") { const response = await fetch( `https://604b3120ee7cb900176a185c.mockapi.io/words/${id}` ); const jsonResponse = await response.json(); console.log(jsonResponse); return prev + " " + jsonResponse.value; } async function getAll() { const response1 = await sendRequest(1); const response2 = await sendRequest(2, response1); const response3 = await sendRequest(3, response2); console.log("getAll :>>", response3); } getAll(); |