Asenkron JavaScript: Geri Çağırma Cehenneminden Async ve Await'e

Yayınlanan: 2022-03-11

Başarılı bir web uygulaması yazmanın anahtarlarından biri, sayfa başına düzinelerce AJAX araması yapabilmektir.

Bu tipik bir asenkron programlama zorluğudur ve asenkron çağrılarla nasıl başa çıkmayı seçtiğiniz, büyük ölçüde uygulamanızı oluşturacak veya bozacaktır ve buna bağlı olarak potansiyel olarak tüm startup'ınız olacaktır.

JavaScript'te asenkron görevleri senkronize etmek çok uzun bir süredir ciddi bir sorundu.

Bu zorluk, herhangi bir JavaScript çerçevesi kullanan ön uç geliştiriciler kadar Node.js kullanan arka uç geliştiricileri de etkiliyor. Asenkron programlama günlük işlerimizin bir parçasıdır, ancak zorluk genellikle hafife alınır ve doğru zamanda düşünülmez.

Asenkron JavaScript'in Kısa Tarihi

İlk ve en basit çözüm , geri aramalar olarak iç içe geçmiş işlevler biçiminde geldi. Bu çözüm, geri arama cehennemi adı verilen bir şeye yol açtı ve çok fazla uygulama hala bunun yanığını hissediyor.

Sonra, Sözler aldık. Bu model, kodun okunmasını çok daha kolay hale getirdi, ancak Kendinizi Tekrar Etme (DRY) ilkesinden çok uzaktı. Uygulamanın akışını düzgün bir şekilde yönetmek için aynı kod parçalarını tekrarlamanız gereken çok fazla durum vardı. Async/await ifadeleri biçimindeki en son ekleme, nihayet JavaScript'te asenkron kodun okunmasını ve yazılmasını diğer herhangi bir kod parçası kadar kolay hale getirdi.

Bu çözümlerin her birinin örneklerine bir göz atalım ve JavaScript'te eşzamansız programlamanın evrimi üzerinde düşünelim.

Bunu yapmak için, aşağıdaki adımları gerçekleştiren basit bir görevi inceleyeceğiz:

  1. Bir kullanıcının kullanıcı adını ve şifresini doğrulayın.
  2. Kullanıcı için uygulama rolleri alın.
  3. Kullanıcı için uygulama erişim süresini günlüğe kaydedin.

Yaklaşım 1: Geri Çağırma Cehennemi (“Kıyamet Piramidi”)

Bu aramaları senkronize etmenin eski çözümü, iç içe geçmiş geri aramalardı. Bu, basit eşzamansız JavaScript görevleri için uygun bir yaklaşımdı, ancak geri arama cehennemi adı verilen bir sorun nedeniyle ölçeklenmiyordu.

Örnek resim: Eşzamansız JavaScript geri çağırma cehennemi anti-pattern

Üç basit görevin kodu şuna benzer:

 const verifyUser = function(username, password, callback){ dataBase.verifyUser(username, password, (error, userInfo) => { if (error) { callback(error) }else{ dataBase.getRoles(username, (error, roles) => { if (error){ callback(error) }else { dataBase.logAccess(username, (error) => { if (error){ callback(error); }else{ callback(null, userInfo, roles); } }) } }) } }) };

Her işlev, önceki eylemin yanıtı olan bir parametreyle çağrılan başka bir işlev olan bir argüman alır.

Çok fazla insan sadece yukarıdaki cümleyi okuyarak beyin donması yaşayacak. Yüzlerce benzer kod bloğuna sahip bir uygulamaya sahip olmak, kodu kendisi yazsa bile, kodu koruyan kişi için daha da fazla sorun yaratacaktır.

Bu örnek, bir database.getRoles öğesinin iç içe geri aramalara sahip başka bir işlev olduğunu fark ettiğinizde daha da karmaşık hale gelir.

 const getRoles = function (username, callback){ database.connect((connection) => { connection.query('get roles sql', (result) => { callback(null, result); }) }); };

Bakımı zor olan bir koda sahip olmanın yanı sıra, bu durumda DRY ilkesinin kesinlikle hiçbir değeri yoktur. Örneğin hata işleme, her işlevde tekrarlanır ve iç içe geçmiş her işlevden ana geri arama çağrılır.

Eşzamansız çağrılar arasında döngü yapmak gibi daha karmaşık eşzamansız JavaScript işlemleri daha da büyük bir zorluktur. Aslında, bunu geri aramalarla yapmanın önemsiz bir yolu yoktur. Bluebird ve Q gibi JavaScript Promise kitaplıklarının bu kadar ilgi görmesinin nedeni budur. Dilin kendisinin sağlamadığı eşzamansız isteklerde ortak işlemleri gerçekleştirmenin bir yolunu sağlarlar.

Yerel JavaScript Sözlerinin devreye girdiği yer burasıdır.

JavaScript Sözleri

Sözler, geri arama cehenneminden kaçmanın bir sonraki mantıklı adımıydı. Bu yöntem geri arama kullanımını ortadan kaldırmadı, ancak işlevlerin zincirlenmesini basitleştirdi ve kodu basitleştirerek okumayı çok daha kolay hale getirdi.

Örnek resim: Eşzamansız JavaScript Sözleri diyagramı

Sözler yerindeyken, eşzamansız JavaScript örneğimizdeki kod şuna benzer:

 const verifyUser = function(username, password) { database.verifyUser(username, password) .then(userInfo => dataBase.getRoles(userInfo)) .then(rolesInfo => dataBase.logAccess(rolesInfo)) .then(finalResult => { //do whatever the 'callback' would do }) .catch((err) => { //do whatever the error handler needs }); };

Bu tür bir basitliği elde etmek için, örnekte kullanılan tüm işlevlerin Promisified olması gerekir. Bir Promise döndürmek için getRoles yönteminin nasıl güncelleneceğine bir göz atalım:

 const getRoles = function (username){ return new Promise((resolve, reject) => { database.connect((connection) => { connection.query('get roles sql', (result) => { resolve(result); }) }); }); };

Yöntemi, iki geri arama ile bir Promise döndürmek için değiştirdik ve Promise kendisi, yöntemden eylemler gerçekleştirir. Şimdi, resolve ve reject geri aramaları sırasıyla Promise.then ve Promise.catch yöntemleriyle eşleştirilecektir.

getRoles yönteminin hala dahili olarak kıyamet fenomeni piramidine eğilimli olduğunu fark edebilirsiniz. Bunun nedeni, Promise döndürmedikleri için veritabanı yöntemlerinin oluşturulma şeklidir. Veritabanı erişim yöntemlerimiz de Promise döndürdüyse, getRoles yöntemi aşağıdaki gibi görünür:

 const getRoles = new function (userInfo) { return new Promise((resolve, reject) => { database.connect() .then((connection) => connection.query('get roles sql')) .then((result) => resolve(result)) .catch(reject) }); };

Yaklaşım 3: Zaman uyumsuz/Bekleme

Kıyamet piramidi, Vaatlerin getirilmesiyle önemli ölçüde hafifletildi. Ancak yine de bir Promise .then ve .catch yöntemlerine aktarılan geri aramalara güvenmek zorundaydık.

Sözler, JavaScript'teki en harika geliştirmelerden birinin yolunu açtı. ECMAScript 2017, JavaScript'te Vaatlerin üzerine zaman async ve await ifadeleri biçiminde sözdizimsel şeker getirdi.

Bu kod örneğinin gösterdiği gibi, Promise tabanlı kodu eşzamanlıymış gibi, ancak ana iş parçacığını engellemeden yazmamıza izin veriyorlar:

 const verifyUser = async function(username, password){ try { const userInfo = await dataBase.verifyUser(username, password); const rolesInfo = await dataBase.getRoles(userInfo); const logStatus = await dataBase.logAccess(userInfo); return userInfo; }catch (e){ //handle errors as needed } };

Çözüm için Promise verilmesini beklemeye yalnızca zaman async işlevler içinde izin verilir, bu da, verifyUser zaman async function kullanılarak tanımlanması gerektiği anlamına gelir.

Ancak, bu küçük değişiklik yapıldıktan sonra, diğer yöntemlerde ek değişiklikler olmadan herhangi bir Promise await .

Async - Bir Sözün Uzun Süredir Beklenen Çözümü

Zaman uyumsuz işlevler, JavaScript'te eşzamansız programlamanın evrimindeki bir sonraki mantıksal adımdır. Kodunuzu çok daha temiz ve bakımı daha kolay hale getirecekler. Bir işlevi zaman async olarak bildirmek, her zaman bir Promise döndürmesini sağlar, böylece artık bunun için endişelenmenize gerek kalmaz.

Neden bugün JavaScript zaman async işlevini kullanmaya başlamalısınız?

  1. Ortaya çıkan kod çok daha temiz.
  2. Hata işleme çok daha basittir ve diğer tüm eşzamanlı kodlarda olduğu gibi try / catch dayanır.
  3. Hata ayıklama çok daha basittir. Bir .then bloğu içinde bir kesme noktası ayarlamak, bir sonraki .then geçmez çünkü bu sadece senkron kodda ilerler. Ancak, await aramalar arasında senkronize aramalarmış gibi adım atabilirsiniz.