Asenkron JavaScript: Geri Çağırma Cehenneminden Async ve Await'e
Yayınlanan: 2022-03-11Baş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:
- Bir kullanıcının kullanıcı adını ve şifresini doğrulayın.
- Kullanıcı için uygulama rolleri alın.
- 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.
Üç 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.

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?
- Ortaya çıkan kod çok daha temiz.
- Hata işleme çok daha basittir ve diğer tüm eşzamanlı kodlarda olduğu gibi
try
/catch
dayanır. - 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.