Programlamaya yeni başlıyorsanız, muhtemelen programları, her bloğun belirli bir şey yaptığı ve sonucunu ilettiği, böylece bir sonraki bloğun çalışabileceği ve bu şekilde devam eden bir dizi ardışık mantık blokları olarak düşünüyorsunuzdur. çoğunlukla haklısınız, çoğu program sıralı bir şekilde çalışır, bu model yazması ve bakımı kolay programlar oluşturmamıza olanak tanır. Ancak bu sıralı modelin işe yaramayacağı veya optimal olmayacağı belirli kullanım durumları vardır. Örnek olarak bir kitap okuyucu uygulamasını düşünün. Bu uygulama, bir kelimenin tüm geçişlerini bulma, yer imleri arasında gezinme ve benzeri gibi birkaç gelişmiş özelliğe sahiptir. Şimdi kullanıcının şu anda uzun bir kitap okuduğunu ve "The" gibi yaygın bir kelimenin geçtiği tüm yerleri aramaya karar verdiğini hayal edin. Uygulamanın normalde o kelimenin tüm geçişlerini bulması ve dizine eklemesi birkaç saniye sürecektir. Sıralı bir programda kullanıcı, arama işlemi tamamlanana kadar uygulamayla etkileşime giremez (sayfayı değiştiremez veya bir metni vurgulayamaz). Umarım bunun ideal bir kullanıcı deneyimi olmadığını görebilirsiniz!
Diyagram, kitap okuyucu uygulamasının tipik bir yürütme akışını göstermektedir. Kullanıcı uzun süren bir işlem başlatırsa (bu durumda büyük bir kitapta "the" kelimesinin geçtiği tüm yerler aranır), uygulama bu işlem süresince "donar". Bu durumda kullanıcı, arama işlemi bitene kadar sonuçsuz bir sonraki yer imi düğmesini tıklamaya devam edecek ve tüm işlemler aynı anda yürürlüğe girecek ve son kullanıcıya uygulamanın geciktiği hissini verecektir.
Bu örneğin daha önce tanıttığımız sıralı modele tam olarak uymadığını fark etmiş olabilirsiniz. Çünkü buradaki işlemler birbirinden bağımsızdır. Kullanıcının bir sonraki yer imine gitmek için "the" harfinin kaç kez geçtiğini bilmesine gerek yoktur, dolayısıyla işlemlerin yürütülme sırası gerçekten önemli değildir. Bir sonraki yer imine gitmeden önce arama işleminin bitmesini beklememize gerek yok. Önceki yürütme akışındaki olası bir iyileştirme şu mantığa dayanmaktadır: uzun arama işlemini arka planda çalıştırabilir, gelen işlemlere devam edebiliriz ve uzun işlem bittiğinde kullanıcıyı kolayca bilgilendirebiliriz. Yürütme akışı şu şekilde olur:
Bu yürütme akışıyla kullanıcı deneyimi önemli ölçüde iyileştirildi. Artık kullanıcı uzun süren bir işlemi başlatabilir, uygulamayı normal şekilde kullanmaya devam edebilir ve işlem bittiğinde bildirim alabilir. Bu asenkron programlamanın temelidir.
Javascript, diğer dillerin yanı sıra, aklınıza gelebilecek hemen hemen tüm eşzamansız davranışları elde etmek için kapsamlı API'ler sağlayarak bu eşzamansız programlama stilini destekler. Günün sonunda, Javascript doğası gereği eşzamansız bir dil olmalıdır. Önceki örneğe bakarsak, asenkron mantık, tüm kullanıcı etkileşimi uygulamalarının temelinde yer alır ve Javascript, öncelikle programların çoğunun kullanıcı eylemlerine yanıt vermekle ilgili olduğu tarayıcıda kullanılmak üzere oluşturulmuştur.
Aşağıdakiler size Asenkron Javascript hakkında kısa bir rehber verecektir:
Geri aramalar
Tipik bir programda genellikle bir takım işlevler bulacaksınız. Bir işlevi kullanmak için onu bir dizi parametreyle çağırırız. İşlev kodu yürütülecek ve bir sonuç döndürecektir; sıra dışı bir şey değildir. Asenkron programlama bu mantığı biraz değiştirir. Kitap okuyucu uygulaması örneğine dönecek olursak, işlemin bilinmeyen bir süre alması nedeniyle arama işlemi mantığını uygulamak için normal bir fonksiyon kullanamıyoruz. Normal bir fonksiyon temel olarak işlem yapılmadan önce geri dönecektir ve bu bizim beklediğimiz davranış değildir. Çözüm, arama işlemi tamamlandıktan sonra yürütülecek başka bir işlevi belirlemektir. Bu, programımızın akışına normal şekilde devam edebileceği ve arama işlemi bittiğinde belirtilen işlevin kullanıcıyı arama sonuçları konusunda bilgilendirmek için yürütüleceği için kullanım durumumuzu modeller. Bu fonksiyona geri çağırma fonksiyonu diyoruz:
// Search occurrences function
function searchOccurrences(word, callback) {
try {
// search operation logic, result is in result variable
//....
callback(null, word, result);
} catch (err) {
callback(err);
}
}
// Search occurrences callback function
function handleSearchOccurrencesResult(err, word, result) {
if (err) {
console.log(`Search operation for ${word} ended with an error`);
} else console.log(`Search results for ${word}: ${result}`);
return;
}
searchOccurrences("the", handleSearchOccurrencesResult);
Öncelikle arama işlemi fonksiyonu olan searchOccurrences'ı tanımlıyoruz. Aranacak kelimeyi ve arama işlemi tamamlandıktan sonra yürütülecek işlev olacak ikinci bir "geri arama" parametresini alır. Arama işlemi işlevi kasıtlı olarak soyut tutuldu; yalnızca iki olası sonucuna odaklanmamız gerekiyor: ilk durum, her şeyin başarılı olduğu durumdur ve sonuç değişkeninde aramanın sonucunu buluruz. Bu durumda, geri çağırma fonksiyonunu aşağıdaki parametrelerle çağırmamız yeterlidir: ilk parametre null, yani hiçbir hatanın oluşmadığı anlamına gelir, ikinci parametre aranan kelimedir ve üç parametreden üçüncü ve belki de en önemli parametredir., arama işleminin sonucudur.
İkinci durum ise bir hatanın meydana geldiği durumdur, bu aynı zamanda arama işleminin gerçekleştirildiği ve geri çağırma fonksiyonunu çağırmamız gereken durumdur. Herhangi bir hatayı engellemek için try ve catch bloğu kullanırız ve catch bloğundaki hata nesnesiyle geri çağırma işlevini çağırırız.
Daha sonra geri çağırma fonksiyonu olan handSearchOccurrences'ı tanımladık, mantığını oldukça basit tuttuk. Bu sadece konsola bir mesaj yazdırma meselesidir. Ana fonksiyonda herhangi bir hata oluşup oluşmadığını görmek için öncelikle “err” parametresini kontrol ediyoruz. Bu durumda kullanıcıya arama işleminin bir hatayla sonuçlandığını bildiririz. Herhangi bir hata oluşmadıysa, arama işleminin sonucunu içeren bir mesaj yazdırırız.
Son olarak searchOccurrences fonksiyonunu “the” kelimesiyle çağırıyoruz. İşlev artık ana programı engellemeden normal şekilde çalışacak ve arama tamamlandıktan sonra geri arama yürütülecek ve arama sonucunu veya hata mesajını içeren sonuç mesajını alacağız.
Burada yalnızca main içindeki result değişkenine ve geri çağırma işlevlerine erişebildiğimizi belirtmek önemlidir. Eğer böyle bir şey denersek:
let result;
function searchOccurrences(word, callback) {
try {
// search operation logic, result is in searchResult variable
//....
result = searchResult;
callback(null, word, result);
} catch (err) {
callback(err);
}
}
searchOccurrences("the", handleSearchOccurrencesResult);
console.log(result);
programlar searchOccurrences işlevinin yürütülmesini beklemediğinden yazdırmanın sonucu tanımsız olacaktır. Sonuç değişkeni ana fonksiyon içinde atanmadan önceki print ifadesi olan bir sonraki talimata geçer. Sonuç olarak atanmamış sonuç değişkenini yazdıracağız.
Dolayısıyla bu mantığa dayanarak, sonuç değişkenini kullanan tüm kodu geri çağırma fonksiyonunun içinde tutmalıyız. Bu şu anda bir sorun gibi görünmeyebilir ancak hızla gerçek bir soruna dönüşebilir. Sırayla çalışması gereken bir asenkron işlevler zincirimizin olduğu durumu hayal edin. Tipik geri çağırma mantığında şunun gibi bir şey uygularsınız:
functionA(function (err, resA) {
///......
functionB(resA, function (err, resB) {
///......
functionC(resB, function (err, resC) {
///......
functionD(resC, function (err, resD) {
///......
});
});
});
});
Her geri aramanın bir hata parametresi olduğunu ve her hatanın ayrı ayrı ele alınması gerektiğini unutmayın. Bu, yukarıda zaten karmaşık olan kodu daha da karmaşık ve bakımı zor hale getirir. Umarım Promises geri arama cehennemi sorununu çözmek için buradadır, bundan sonra bu konuyu ele alacağız.
Vaatler
Vaatler geri aramaların üzerine kuruludur ve benzer şekilde çalışır. Geri arama cehennemi gibi geri aramalarla ilgili göze çarpan birkaç sorunu çözmek için ES6 özelliklerinin bir parçası olarak tanıtıldılar. Vaatler, başarılı bir şekilde tamamlandığında (çözümle) ve hatalar oluştuğunda (reddet) çalışan kendi işlevlerini sağlar. Aşağıda vaatlerle uygulanan searchOccurrences örneği gösterilmektedir:
// Search occurrences function
function searchOccurrences(word) {
return new Promise((resolve, reject) => {
try {
// search operation logic, result is in result variable
//....
resolve(word, result);
} catch (err) {
reject(err);
}
});
}
searchOccurrences("the")
.then((word, result) => {
console.log(`Search results for ${word}: ${result}`);
})
.catch((err) => {
console.log(`Search operation ended with an error`);
});
Uyguladığımız değişikliklerin üzerinden geçelim:
searchOccurrences işlevi bir söz döndürür. Sözün içinde aynı mantığı koruyoruz: Hem başarılı bir yürütmeyi hem de hatalı yürütmeyi yöneten tek bir geri çağırma işlevi yerine, geri çağrılarımızı temsil eden çözümleme ve reddetme iki işlevimiz var. Promises iki sonucu ayırır ve ana işlevi çağırırken temiz bir sözdizimi sağlar. Çözme işlevi, “then” anahtar sözcüğünü kullanarak ana işleve “bağlanır”. Burada sadece çözümleme fonksiyonunun iki parametresini belirleyip arama sonucunu yazdırıyoruz. Benzer bir şey reddetme işlevi için de geçerlidir, "catch" anahtar sözcüğü kullanılarak bağlanabilir. Umarız kod okunabilirliği ve temizliği açısından vaat edilen avantajları takdir edersiniz. Hala tartışıyorsanız, eşzamansız işlevleri birbiri ardına çalışacak şekilde zincirleyerek geri arama cehennemi sorununu nasıl çözebileceğimize bir göz atın:
searchOccurrences("the")
.then(searchOccurrences("asynchronous"))
.then(searchOccurrences("javascript"))
.then(searchOccurrences("guide"))
.catch((err) => {
console.log(`Search operation ended with an error`);
});
Eşzamansız/Beklemede
Async/Await, Javascript'teki asenkron araç kemerimize eklenen en son eklentidir. ES8 ile birlikte sunulan bu özellikler, eşzamansız bir işlemin yürütülmesini basitçe "bekleyerek" eşzamansız işlevlerin üzerinde yeni bir soyutlama katmanı sağlar. Program akışı, asenkron işlemden bir sonuç dönene kadar o talimatta bloke edilir ve ardından program bir sonraki talimatla devam eder. Senkronize yürütme akışını düşünüyorsanız haklısınız. Tam çembere geldik! Eşzamansız/beklemede, eşzamanlı programlamanın basitliğini eşzamansız dünyaya getirmeye çalışır. Lütfen bunun yalnızca programın yürütülmesinde ve kodunda algılandığını unutmayın. Başlık altında her şey aynı kalıyor, Async/await hala sözler kullanıyor ve geri aramalar onların yapı taşlarıdır.
Örneğimizin üzerinden geçelim ve onu Async/await kullanarak uygulayalım:
async function searchOccurrences(word) {
try {
// search operation logic, result is in result variable
//....
return result;
} catch (err) {
console.log(`Search operation for ${word} ended with an error`);
}
}
const word = "the";
const result = await searchOccurrences(word, handleSearchOccurrencesResult);
console.log(`Search results for ${word}: ${result}`);
Kodumuz pek değişmedi, burada dikkat edilmesi gereken önemli nokta, searchOccurrences fonksiyon bildiriminden önceki “async” anahtar kelimesidir. Bu, fonksiyonun eşzamansız olduğunu gösterir. Ayrıca, searchOccurrences işlevini çağırırken "await" anahtar sözcüğüne dikkat edin. Bu, programa, programın bir sonraki talimata geçmeden önce, sonuç döndürülene kadar fonksiyonun yürütülmesini beklemesi talimatını verecektir; başka bir deyişle, sonuç değişkeni, her zaman searchOccurrences fonksiyonunun döndürülen değerini tutacaktır ve sözünü tutmayacaktır. işlev, bu anlamda, Async/Await'in Promises gibi beklemede bir durumu yoktur. Yürütme tamamlandıktan sonra print ifadesine geçiyoruz ve bu sefer sonuç aslında arama işleminin sonucunu içeriyor. Beklendiği gibi yeni kod, senkronizeymiş gibi aynı davranışa sahiptir.
Akılda tutulması gereken bir diğer küçük nokta da, artık geri çağırma işlevlerine sahip olmadığımızdan, searchOccurrences hatasını aynı işlevin içinde işlememiz gerektiğidir, çünkü hatayı yalnızca geri çağırma işlevine yayıp orada işleyemiyoruz. Burada sadece örnek olması açısından bir hata durumunda bir hata mesajı yazdırıyoruz.
Özet
Bu makalede Javascript'te eşzamansız mantığı uygulamak için kullanılan farklı yaklaşımları inceledik. Düzenli eşzamanlı programlama tarzından eşzamansız modele neden geçmemiz gerektiğine dair somut bir örneği keşfederek başladık. Daha sonra eşzamansız Javascript'in ana yapı taşları olan geri aramalara geçtik. Geri aramaların sınırlamaları bizi, bu sınırlamaları aşmak için yıllar içinde eklenen, çoğunlukla vaatler ve Async/await olmak üzere farklı alternatiflere yönlendirdi. Eşzamansız mantık, ister harici bir API çağırıyor olun, ister veritabanı sorgusu başlatın, yerel dosya sistemine yazıyor olun, hatta oturum açma formunda kullanıcı girişini bekliyor olun, web üzerinde herhangi bir yerde bulunabilir. Umarım artık temiz ve bakımı kolay Asenkron Javascript yazarak bu sorunların üstesinden gelme konusunda kendinizi daha güvende hissedersiniz!
Bu makaleyi beğendiyseniz lütfen teknolojiye nasıl girilebileceğine dair çeşitli konuları tartıştığımız CLA Bloguna göz atın. Ayrıca önceki ücretsiz atölyelerimiz için youtube kanalımıza göz atın ve bizi sosyal medyada takip edin. -labs-academy/) böylece yaklaşan etkinlikleri kaçırmazsınız!