11. Olay tabanlı programlama ve asenkron işlevler

Asenkron işlev, yürütülmesi başlatılan ancak sonucunun beklenmediği bir işlevdir. Yürütme tamamlandığında, asenkron işlev bir olay yayar ve sonucunu bu olay aracılığıyla iletir.
Bu çalışma şekli, bir web tarayıcısı içinde yürütülmeye çok uygundur. Nitekim, bir tarayıcı içinde yürütülen uygulama, olay tabanlı bir uygulamadır: uygulama, çoğunlukla kullanıcı tarafından tetiklenen olaylara (tıklamalar, fare hareketleri, metin girişi vb.) tepki verir. Tarayıcı içinde çalıştırılan JavaScript uygulamaları, HTTP protokolü aracılığıyla harici hizmetlerle iletişim kurmak zorundadır. Javascript’in HTTP yerel işlevleri asenkron çalışır: bu işlevler başlatılır ve talep edilen harici hizmetten yanıt alındığında, uygulama tarafından yönetilen olaylar kümesine eklenen bir olayla bildirilir.
Aşağıdaki komut dosyaları bir tarayıcı tarafından değil, [node.js] tarafından çalıştırılacaktır. [node.js] de bir tarayıcı gibi olay tabanlı bir çalışma moduna sahiptir:
- [node.js], bir tarayıcı gibi, bir komut dosyasını çalıştırmak için bir olay döngüsü kullanır;
- komut dosyasının ana kodunun yürütülmesi, gerçekleştirilen ilk olaydır;
- eğer bu ana kod asenkron görevler başlatmışsa, bu asenkron görevler tamamlanana kadar yürütme devam eder. Bu görevler tamamlandıklarında bir olay yayarlar. Bu olaylar olay döngüsünde kuyruğa alınır;
- ana komut dosyası, asenkron işlemlerin sonuçlarını almak istiyorsa bu olaylara abone olmalıdır;
- komut dosyası, kendisi tarafından gönderilen tüm olaylar işlendiğinde tamamlanmış sayılır;
11.1. [async-01] komut dosyası
Aşağıdaki komut dosyası, asenkron eylem içeren bir komut dosyasının davranışını göstermektedir.
'use strict';
// ithalatlar
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// başlangıç
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// setTimeout, 1000 ms'lik bir zamanlayıcı başlatır (2. parametre) ve bu zamanlayıcının numarasını hemen döndürür
// zamanlayıcı 1000 ms'yi tamamladığında, bir olay yayar ve bu olay çalışma zamanı kuyruğuna eklenir
// Olay çalışma zamanı tarafından işlendiğinde, işlev (1. parametre) yürütülür
setTimeout(function () {
// Zamanlayıcı 0 değerine ulaştığında bu kod çalıştırılacaktır
console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
// zamanlayıcının dahili işlevinin mesajından önce görüntülenecektir
console.log("[fin du code principal du script],", heure(débutScript));
// saat ve süre görüntüleme aracı
function heure(début) {
// geçerli saat
const now = moment(Date.now());
// saat biçimlendirme
let result = "heure=" + now.format("HH:mm:ss:SSS");
// süre hesaplanmalı mı?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// saat + süre biçimlendirme
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// sonuç
return result;
}
- 4. satır: tarihleri biçimlendirebilmek için [moment] kütüphanesi içe aktarılır (27. satır);
- 5. satır: süreleri biçimlendirebilmek için [sprintf-js] kütüphanesi içe aktarılır (34. satır);
- 8. satır: komut dosyasının başlangıç saati kaydedilir;
- 9. satır: 20-34. satırlardaki [heure] yöntemi kullanılarak bu saat görüntülenir;
- satır 14-17: [setTimeout] işlevinin iki parametresi vardır (f, süre): f, [durée] milisaniye geçtiğinde çalıştırılan bir işlevdir;
- 14. satır: komut dosyasının çalıştırılmasıyla [setTimeout] işlevi yürütülür:
- 17. satır: 1000 ms'lik bir zamanlayıcı başlatılır ve geri sayım, daha sonra 0'a ulaşana kadar devam eder. Zamanlayıcı başlatılır ve geri sayım başlar başlamaz [setTimeout] işlevi sona erer. Bu fonksiyon, geri sayımın bitmesini beklemez. Kullanılan zamanlayıcıyı tanımlayan bir numara döndürür ve yürütme bir sonraki komuta, yani 20. satıra geçer. Burada, [setTimeout] fonksiyonunun sonucu kullanılmaz;
- 16. satır: Bu mesaj, [setTimeout] işlevinin 1000 ms'lik süresinin sonunda görüntülenecektir;
- 15-16. satırlar: [setTimeout] işlevinin ilk parametresi olan f işlevi, 1000 ms'lik süre dolduğunda çalıştırılacaktır. Ardından 16. satırdaki mesaj görüntülenecektir;
- 20. satır: Bu mesaj, 16. satırdaki mesajdan önce görüntülenecektir;
Saat fonksiyonu:
- 23. satır: İşlev, süresini görüntülemesi gereken bir işlemin başlangıç saatini belirten isteğe bağlı bir parametre olan [heure]'i kabul eder;
- 25-27. satırlar: o anki saat hesaplanır ve biçimlendirilir;
- 29. satır: [début] parametresi varsa, süre hesaplanmalıdır;
- satır 30: işlemin süresi. Milisaniye cinsinden bir sayı elde edilir;
- satır 31-32: Bu milisaniye sayısı saniye ve milisaniyeye ayrılır;
- 34. satır: Süre, saate eklenir;
Çalıştırma
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-01.js"
[début du script], heure=09:26:40:238
[fin du code principal du script], heure=09:26:40:246, durée= 0 seconde(s) et 11 millisecondes
[fin de l'action asynchrone setTimeout], heure=09:26:41:249, durée= 1 seconde(s) et 14 millisecondes
[Done] exited with code=0 in 1.672 seconds
- 4. satırda, asenkron eylem [setTimeOut]'in, komut dosyasının ana kodunun bitiminden yaklaşık 1 saniye sonra tamamlandığı görülmektedir;
- 6. satır: 3. satırda görüntülenen saat, ana kodun bittiği zamana aittir. Ana kod asenkron görevler başlatmışsa, komut dosyası ancak tüm asenkron görevler yürütüldükten sonra tamamlanır. 6. satırda görüntülenen süre, komut dosyasının toplam yürütme süresidir (ana kod + asenkron görevler);
[setTimeout] işlevi, [node.js] ortamında asenkron görevleri simüle etmemizi sağlayacaktır. Nitekim, [setTimeout] işlevi bir asenkron görev gibi davranır:
- fonksiyonların olağan mekanizması (return) aracılığıyla hemen bir sonuç, bu örnekte bir zamanlayıcı numarası, döndürür;
- daha sonra (yukarıdaki örnekte henüz böyle değil) [node.js]'in olay döngüsü tarafından işlenen olaylar aracılığıyla başka sonuçlar da döndürebilir;
- aşağıda ele alınacak çoğu durumda, bu olayların sayısı iki olacaktır:
- [success] olarak adlandırılabilecek bir olay; bu, yapması gereken işi başarıyla tamamlayan asenkron görev tarafından yayınlanacaktır. Görevin sonucu olan bir veri, yayınlanan olayla ilişkilendirilir;
- [failure] olarak adlandırabileceğimiz bir olay; bu, yapması gereken işi gerçekleştiremeyen asenkron görev tarafından yayınlanacaktır. Yayınlanan olaya, genellikle hatayı açıklayan bir nesne olan bir veri eşlik eder. Örneğin, asenkron bir internet görevinde ortaya çıkabilecek hatalar arasında “ağ kullanılamıyor”, “sunucu makinesi mevcut değil”, “zaman aşımı” vb. sayılabilir.
- Asenkron görevi başlatan ana kod, bu görevin yayınlayabileceği olaylara abone olabilir. Bu olaylardan biri yayınlandığında, ana kod buna ilişkin bir bildirim alır ve olayı işlemek üzere tasarlanmış belirli bir fonksiyonun yürütülmesini tetikleyebilir. Bu fonksiyon, parametre olarak asenkron görevin yayınlanan olaya ilişkilendirdiği veriyi alır;
11.2. [async-02] komut dosyası
Bu komut dosyasında, [setTimeout] adlı asenkron işlev, olaylara abone olan kodlara veri iletmek için olaylar yayınlayacaktır.
[node.js] olaylarına erişim için ek kütüphaneler gereklidir. [events] kütüphanesini seçiyoruz ve bunu [npm] ile birlikte kuruyoruz:

[async-02] komut dosyası şu şekildedir:
''use strict';
// asenkron işlevler bir olay yayınlayarak sonuç verebilir
// ana kod, yayınlanan olaylara abone olarak bu sonuçları alabilir
// ithalatlar
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// başlangıç
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// bir olay yayınlayıcı
const eventEmitter = new EventEmitter();
// setTimeout, 1000 ms'lik bir zamanlayıcıyı başlatır (2. parametre) ve bu zamanlayıcının numarasını hemen döndürür
// zamanlayıcı 1000 ms'yi tamamladığında, çalışma zamanı kuyruğuna eklenen bir olay yayar
// Olay çalışma zamanı tarafından işlendiğinde, işlev (1. parametre) yürütülür
setTimeout(function () {
// Zamanlayıcı 0 değerine ulaştığında bu kod çalıştırılacaktır
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// bir sonucun hazır olduğunu bildirmek için bir olay gönderilir
eventEmitter.emit("timer1Success", { success: 4 });
// başka bir sonucun hazır olduğunu bildirmek için başka bir olay gönderilir
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// [timer1Success] olayına abone olunur
eventEmitter.on('timer1Success', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
// [timer1Failure] olayına abone olunur
eventEmitter.on('timer1Failure', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
// [timer1] ile ilişkili işlev tarafından gönderilen olay mesajlarından önce görüntülenecektir
console.log("[fin du code principal du script],", heure(débutScript));
// saat ve süre görüntüleme yardımcı programı
function heure(début) {
// geçerli saat
const now = moment(Date.now());
// saat biçimlendirme
let result = "heure=" + now.format("HH:mm:ss:SSS");
// süre hesaplanmalı mı?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// saat + süre biçimlendirme
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// sonuç
return result;
}
Açıklamalar
- 9. satırda, [events] kütüphanesinden [EventEmitter] sınıfı içe aktarılır. Bu bir yenilik: şimdiye kadar sadece sabit nesneler ve işlevler içe aktarmıştık;
- 15. satır: [new] anahtar sözcüğüyle [EventEmitter] sınıfını örneklendirerek [node.js] olay yayınlayıcısı oluşturulur;
- 20-27. satırlar: asenkron [setTimeout] işlevi. Bu işlev, yürütülürken iki olay yayınlayacaktır:
- 24. satırda, {success : 4} nesnesini değer olarak içeren [timer1Success] olayı;
- 26. satırda, {failure : 6} nesnesini içeren [timer1Failure] olayı;
- asenkron bir işlev istediği kadar olay gönderebilir. Daha önce, bu işlevin çoğu zaman [success, failure] olaylarından birini gönderdiğini, burada olduğu gibi ikisini birden göndermediğini belirtmiştik;
- 20. satır: [setTimeout]'in yürütülmesi anında gerçekleşir: bir zamanlayıcı başlatılır ve zamanlayıcının numarası çağıran koda döndürülür. Olayların yayılması daha sonra gerçekleşir; burada 1 saniye sonra;
- Olaylar ortaya çıktığında bunları değerlendirecek bir kod yoksa, olayların yayınlanması anlamsızdır. Bu nedenle ana kod, bu olayları yönetmek, özellikle de bu olaylarla ilişkili verileri almak istiyorsa, her iki [timer1Success, timer1Failure] olayına da abone olmalıdır;
- 30-32. satırlar: Ana kod, [timer1Success] olayına abone olur. [node.js] olay kaynağı bu olayı işlediğinde, [timer1Success] olayına ilişkin veriyi (burada [result] olarak adlandırılan) ona aktararak [eventEmitter.on] yönteminin ikinci parametresi olan işlevi çağıracaktır;
- 31. satır: Olay işleme fonksiyonu, olaya ilişkin verinin jSON değerini ve o anki saati görüntüleyecektir;
- 35-37. satırlar: Benzer bir kodla, ana kod [timer1Failure] olayına abone olur;
- Bir olaya abonelik (1. parametre), [callback] işlevinin kodunu (2. parametre) hemen yürütmez. Bu işlev, ancak olay gerçekleştiğinde yürütülecektir;
- 40. satır: Komut dosyasının ana kodu sona ermiştir, ancak ana kod asenkron bir görev başlattığı için komut dosyasının kendisi henüz tamamlanmamıştır. Genel komut dosyası, bu asenkron görev sona erdikten sonra tamamlanacaktır;
Elde edilen sonuçlar da bunu göstermektedir:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-02.js"
[début du script], heure=09:34:58:909
[fin du code principal du script], heure=09:34:58:916, durée= 0 seconde(s) et 10 millisecondes
[setTimeout, fin du timer d'1 s], heure=09:34:59:929, durée= 1 seconde(s) et 23 millisecondes
la fonction asynchrone du timer a rendu le résultat [{"success":4}], heure=09:34:59:931, durée= 1 seconde(s) et 25 millisecondes, via l'événement [timer1Success]
la fonction asynchrone du timer a rendu le résultat [{"failure":6}], heure=09:34:59:932, durée= 1 seconde(s) et 26 millisecondes, via l'événement [timer1Failure]
[Done] exited with code=0 in 1.627 seconds
- 3. satır: komut dosyasının başlamasından 10 ms sonra ana kodun sonu;
- 4. satır: 1000 ms'lik zamanlayıcıya kapsüllenmiş işlevin başlangıcı, komut dosyasının başlamasından yaklaşık 1 saniye sonra;
- satır 5: [‘timer1Success’] olayının işlenmesi, 2 ms sonra;
- satır 6: [‘timer1Failure’] olayının işlenmesi, [‘timer1Success’] olayından 1 ms sonra;
- satır 8: toplam süresi 1,627 saniye olan genel komut dosyasının sonu;
11.3. [async-03] komut dosyası
Aşağıdaki komut dosyası, [node.js] olay döngüsünün başka bir yönünü göstermektedir:
- döngü, olayları genellikle geldikleri sırayla arka arkaya yürütür. Bazı OS'ler olaylara öncelik atar; bu durumda olaylar geldikleri sırayla değil, öncelik sırasına göre işlenir;
- döngü bir seferde yalnızca bir olayı yürütür. Bir sonraki olay, bir öncekinin işlenmesi tamamlandığında işlenir. Dolayısıyla bir olay tabanlı sistemde, işlemciyi uzun süre meşgul eden kod yazmaktan kaçınılmalıdır; aksi takdirde olaylar meydana geldiklerinde değil, olay döngüsü onlara ulaştığında işlenir. Bu durumda uygulama “tepki verme” açısından yetersiz kalır;
[async-03] komut dosyası bu durumun bir örneğini göstermektedir:
''use strict';
// asenkron işlevler bir olay yayınlayarak sonuç verebilir
// ana kod, yayınlanan olaylara abone olarak bu sonuçları alabilir
// ithalatlar
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// başlangıç
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// bir olay yayınlayıcı
const eventEmitter = new EventEmitter();
// setTimeout, 1000 ms'lik bir zamanlayıcıyı başlatır (2. parametre) ve bu zamanlayıcının numarasını hemen döndürür
// zamanlayıcı 1000 ms'yi tamamladığında, çalışma zamanı kuyruğuna eklenen bir olay yayar
// Olay çalışma zamanı tarafından işlendiğinde, işlev (1. parametre) yürütülür
setTimeout(function () {
// Bu kod, zamanlayıcı 0 değerine ulaştığında çalıştırılacaktır
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// bir sonuç hazır olduğunu bildirmek için bir olay gönderilir
eventEmitter.emit("timer1Success", { success: 4 });
// başka bir sonucun hazır olduğunu bildirmek için başka bir olay gönderilir
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// [timer1Success] olayına abone olunur
eventEmitter.on('timer1Success', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
// [timer1Failure] olayına abone olun
eventEmitter.on('timer1Failure', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
// biraz yoğun çalışan bir senkron kod, ana kodun [timer1] bitmeden tamamlanmasını engelledi
for (let i = 0; i < 1000000; i++) {
for (let j = 0; j < 10000; j++) {
i + i ^ 2 + i ^ 3;
}
}
// [timer1] ile ilişkili işlev tarafından gönderilen olay mesajlarından önce görüntülenecektir
console.log("[fin du script],", heure(débutScript));
// saat ve süre görüntüleme yardımcı programı
function heure(début) {
...
}
Açıklamalar
- Bu kod, önceki örnek olan [async-02]'e 39-44. satırların eklendiği halidir;
- 20-27. satırlar: [setTimeout] işlevi, bir saniye sonra dahili bir asenkron işlevi yürütmek üzere programlanmıştır. Bu bir saniye geçtikten sonra, zamanlayıcının asenkron işlevinin yürütülmesi hemen gerçekleşmez: bunu talep etmek için yürütme döngüsüne bir olay yerleştirilir. Yürütme döngüsü başka bir olayı işlemekle meşgulse, zamanlayıcının asenkron işlevinin yürütülmesi beklemek zorunda kalır;
- 20-27. satırlar: [setTimeout] işlevi, zamanlayıcısını bir saniyelik gecikmeyle kurar kurmaz işlemciyi serbest bırakır ve kontrolü çağıran koda geri verir. Çağıran kod, olay abonelikleri olan ve yürütme süresi ihmal edilebilir düzeyde olan 30-37. satırlarla devam eder;
- ana kod, 1010 yinelemeden oluşan bir döngü oluşturan 40-44. satırlarla devam eder. Zamanlayıcı “1 saniyelik süre sonu” olayını yayınladığında bu kod yürütülmekte olacaktır. Bu olay daha sonra olay döngüsüne eklenir, ancak işlenebilmesi için komut dosyasının ana kodunun yürütülmesinin tamamlanmasını beklemesi gerekir;
- 47. satır: komut dosyasının ana kodunun sonu. Bu son görüntülemeden sonra zamanlayıcının bitiş olayı işlenebilecek ve [setTimeout] içindeki asenkron işlev çalıştırılabilecektir;
Komut dosyası şu sonuçları verir:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-03.js"
[début du script], heure=08:55:02:665
[fin du code principal du script], heure=08:55:11:789, durée= 9 seconde(s) et 131 millisecondes
[setTimeout, fin du timer d'1 s], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes
la fonction asynchrone du timer a rendu le résultat [{"success":4}], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes, via l'événement [timer1Success]
la fonction asynchrone du timer a rendu le résultat [{"failure":6}], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes, via l'événement [timer1Failure]
[Done] exited with code=0 in 9.796 seconds
Yorumlar
- 3. satır: Komut dosyasının ana kodunun yürütülmesinin 9 saniye sürdüğü görülmektedir. Bu süre zarfında meydana gelmiş olabilecek olaylar, olay döngüsünde beklemeye alınmıştır;
- 4. satır: [fin du timer] olayının, ana kodun bitiminden 5 ms sonra işlendiği görülmektedir. Bu olay, komut dosyasının başlamasından yaklaşık 1 saniye sonra yayınlanmıştır, ancak nihayetinde işlenebilmesi için 8 saniye daha beklemek zorunda kalmıştır;
Bu örnekten çıkarılacak ders, olay tabanlı bir sistemde bir kodun işlemciyi asla çok uzun süre meşgul etmemesi gerektiğidir. Yürütülmesi uzun süren senkron bir kod varsa, bunu bir olayla tamamlandığını bildirecek daha kısa asenkron görevlere bölmek için bir yol bulmalıyız.
11.4. [async-04] komut dosyası
[async-04] komut dosyası, [Promise] olarak adlandırılan başka bir mekanizmayı, yani bir sonuç vaadini gösterir. Bu mekanizma, [node.js] olaylarını açıkça yönetme ihtiyacını ortadan kaldırır. Bu işlem örtük olarak gerçekleştirilir ve geliştirici bu olayların varlığını göz ardı edebilir. Ancak bu olayları anlamak, ilk bakışta karmaşık görünen [Promise]'in işleyişini daha iyi kavramasına olanak tanıyacaktır.
[Promise] türü bir JavaScript sınıfıdır. Oluşturucusu, parametre olarak geleneksel olarak [resolve] ve [reject] olarak adlandırılan iki parametreyi aktardığı bir asenkron işlevi kabul eder. Bu parametrelerin başka bir adı da olabilir;
- [Promise]'in oluşturucusu iki şey yapar:
- kendisine parametre olarak aktarılan [function(resolve, reject)] işlevinin yürütülmesini başlatmak için bir olay oluşturur, ancak sonucunu beklemeden hemen çağıran koda bir [Promise] nesnesi döndürür. Bu nesnenin dört durumu olabilir:
- [pending]: [Promise]’i oluşturan asenkron işlem henüz tamamlanmadı;
- [fulfilled]: [Promise]'i döndüren asenkron işlem başarıyla tamamlandı;
- [rejected]: [Promise]'i oluşturan asenkron işlem başarısızlıkla sonuçlandı;
- [settled]: [Promise]'i döndüren asenkron eylem tamamlandı;
- kendisine parametre olarak aktarılan [function(resolve, reject)] işlevinin yürütülmesini başlatmak için bir olay oluşturur, ancak sonucunu beklemeden hemen çağıran koda bir [Promise] nesnesi döndürür. Bu nesnenin dört durumu olabilir:
Oluşturucu sonucunu döndürdüğünde, oluşturulan [Promise] nesnesi, asenkron işlevin sonuçlarını bekleyen [pending] durumunda olur;
- (devam)
- 2-5. satırlardaki asenkron görev hemen başlatılır. Asenkron görevler çoğunlukla giriş/çıkış asenkron görevleridir ve şu şekilde ayrılır:
- başka bir bileşenle (örneğin uzak bir sunucu) G/Ç işlemini başlatmak için senkron kodun yürütülmesi;
- bu bileşenin yanıtının beklenmesi;
- bu yanıtın işlenmesi;
- 2-5. satırlardaki asenkron görev hemen başlatılır. Asenkron görevler çoğunlukla giriş/çıkış asenkron görevleridir ve şu şekilde ayrılır:
İşlemci dışındaki bileşenin yanıtını bekleme aşaması (2. aşama) en fazla kaynak tüketen aşamadır. Beklemek yerine:
- dış birimden istenen verinin alındığı bir olayla bildirilecektir;
- 1. aşamayı takip edecek senkron kodda (örnek kodun 7. satırı), bu olaya abone olunacak ve daha sonra bir noktada [node.js] olay döngüsüne geri dönülecektir. Bekleyen olaylar listesindeki bir sonraki olay işlenecektir;
- 2. aşama sırasında, farklı aygıtlarda paralel yürütme gerçekleşir:
- olay döngüsü için işlemci;
- istenen verinin aranması için harici bir birim (disk, veritabanı, uzak sunucu);
- 2. aşamanın sonunda, G/Ç işlemi talep ettiği veriyi elde ettiğinde, G/Ç sonucunun hazır olduğunu belirtmek için bir olay yayınlanır. Bu olay daha sonra olay bekleme listesindeki diğer olaylara eklenir;
- sırası geldiğinde işlenecektir. Bu olaya ilişkin işlev (örnek kodun 7. satırı) bu noktada çalıştırılacaktır;
Bu çalışma şekli, işlemcinin kendisinden daha yavaş bir aygıtın yanıtını beklediği durumlardaki bekleme sürelerini ortadan kaldırır;
- (devamı)
- 2. ve 5. satırlardaki asenkron görev başlatılıp işini tamamladığında, [Promise] yapıcı işlevinin parametre olarak aktardığı iki [resolve, reject] işlevi sayesinde çağıran koda bir sonuç döndürebilir. Kural şöyledir:
- asenkron görev, [resolve(result)] aracılığıyla başarıyı bildirir. Bu, [node.js] olay döngüsüne, [resolved] olarak adlandırılabilecek ve [result]'i ilişkili veri olarak içeren bir olay eklemekle eşdeğerdir;
- asenkron görev, [reject(error)] ile bir hata bildirir. Bu, [node.js] olay döngüsüne, [rejected] olarak adlandırılabilecek ve [error]'i ilişkili veri olarak içeren bir olay eklemekle eşdeğerdir; genellikle bu veri, meydana gelen hatayı detaylandıran bir nesnedir;
- bu nedenle, asenkron fonksiyonun sonucunun hazır olduğunu öğrenmek için çağıran kodun bu iki olaya abone olması gerekir;
- 2. ve 5. satırlardaki asenkron görev başlatılıp işini tamamladığında, [Promise] yapıcı işlevinin parametre olarak aktardığı iki [resolve, reject] işlevi sayesinde çağıran koda bir sonuç döndürebilir. Kural şöyledir:
[Promise] içinde kapsüllenmiş asenkron görevin yürütülmesi tamamlandıktan sonra, [Promise(…)] oluşturucusu tarafından döndürülen [promise] nesnesinin durumu değişir:
- [resolved] olayı, nesneyi [pending] durumundan [resolved] durumuna geçirir;
- [rejected] olayı, durumu [pending]'ten [rejected]'e geçirir;
Asenkron görevin [resolved] ve [rejected] olaylarına abonelik, [Promise] sınıfının yöntemleri kullanılarak aşağıdaki sözdizimiyle yapılır:
promise.then(f1).catch(f2).finally(f3);
burada:
- f1, [promise] durumunun [pending]'ten [resolved]'e geçtiği, yani asenkron görevin işini başarıyla tamamladığı zaman yürütülen bir işlevdir. Bu fonksiyon, asenkron görevin [resolve(result)] komutu tarafından iletilen [result] değerini parametre olarak alır;
- f2, [promise]'in durumu [pending]'ten [rejected]'e geçtiğinde, yani asenkron görevin işini yerine getiremediğinde yürütülen bir işlevdir. Bu fonksiyon, asenkron görevin [reject(errror)] komutu tarafından iletilen [error] değerini parametre olarak alır;
- f3, [then] veya [catch] yöntemlerinin yürütülmesinden sonra çalıştırılan bir işlevdir; dolayısıyla her zaman yürütülür. Herhangi bir parametre almaz;
Bu sözdizimi, abone olunan olayları tamamen gizler. Yine de bu bir aboneliktir ve önceki örnekteki gibi, [f1, f2, f3] işlevlerini hemen yürütmez. Bu işlevler, abone olunan [resolved, rejected] olaylarından biri gerçekleştiğinde yürütülecek ya da yürütülmeyecektir.
[async-04] komut dosyası bu işleyişi göstermektedir:
'use strict';
// bir asenkron fonksiyonun sonuçlarını (başarı, hata) elde etmek mümkündür
// [Promise] sınıfı sayesinde olayları açıkça kullanmadan
// bu sınıf olayları örtük olarak kullanır, ancak bunlar kodda görünmez
// ithalatlar
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// başlangıç
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
// bir söz (promise) kullanılarak asenkron görev tanımı [Promise]
// asenkron görev, oluşturucunun parametresidir [Promise]
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
// günlük
console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
// asenkron kod
setTimeout(function () {
console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
// asenkron görev, [resolve] işleviyle bir sonuç döndürür
// böylece söz başarıyla yerine getirilmiş olur
resolve('[réussite]');
}, 1000)
});
// [promise1] sözünün sonucunu öğrenebiliriz
// bu söz, çözüldüğünde (resolve) veya reddedildiğinde (reject)
// Aşağıdaki komut, [then] yöntemi aracılığıyla [resolved] olayına abonelik oluşturur
// ve [then]X yöntemi aracılığıyla [rejected] olayına
// [finally] yöntemi, ister bir then ister bir catch ifadesinden sonra olsun, yürütülür
promise1.then(result => {
// sözün başarıyla sonuçlanması durumunda [evt resolved]
console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// hata durumu [evt rejected]
console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// her durumda yürütülür
console.log("[promise1.finally]", heure(débutPromise1));
});
// bir söz kullanılarak asenkron görev tanımlama [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
// günlük
console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
// asenkron görev
setTimeout(function () {
console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
// asenkron görev, [reject] işleviyle bir sonuç döndürür
// bu durumda söz başarısız olur
reject('[échec]');
}, 2000)
});
// [promise2] sözünün sonucunu öğrenebiliriz
// bu söz, çözüldüğünde (resolve) veya reddedildiğinde (reject)
promise2.then(result => {
// sözün başarıyla sonuçlandığı durum [evt resolved]
console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// hata durumunda [evt rejected]
console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// her durumda yürütülür
console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
// asenkron işlevlerin mesajlarından ve ilgili olayların mesajlarından önce görüntülenecektir
console.log("[fin du code principal du script],", heure(débutScript));
// yardımcı program
function heure(début) {
// geçerli saat
const now = moment(Date.now());
// saat biçimlendirme
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// süre biçimlendirme
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// sonuç
return result;
}
Açıklamalar
- 18-28. satırlar: bir [Promise promise1] oluşturulur. Asenkron işlevi, bir saniye sonra bir olay aracılığıyla sonucunu döndürür. Bu asenkron işlem başlatıldıktan sonra (zamanlayıcının devreye alınması), sonucun döndürülmesini beklemeden hemen 35. satırdaki koda geçilir;
- 35-44. satırlar: [promise1] işlevinin içindeki asenkron işlevin gönderebileceği iki [resolved, rejected] olayına abone olunur;
- satır 46-71: İkinci bir [promise2] sözü için önceki kod dizisini tekrarlarız;
- 74. satır: Komut dosyasının ana kodu tamamlanmıştır, ancak iki asenkron eylem başlatıldığı için komut dosyası bir bütün olarak henüz tamamlanmamıştır. Olay döngüsüne geri dönülür; burada bir noktada [promise1, promise2] vaatlerinden gelen [resolved, rejected] olaylarından biri gerçekleşecektir. Bu olay işlenecek;
- ardından olay döngüsüne geri dönülür. Ve burada, [promise1, promise2] sözlerinin ikinci olayı olan [resolved, rejected], gerçekleştiğinde işlenecektir;
Çalıştırma
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-04.js"
[début du script], heure=09:39:05:950, durée= 0 seconde(s) et 3 millisecondes
[début fonction asynchrone de promise1], heure=09:39:05:958, durée= 0 seconde(s) et 0 millisecondes
[début fonction asynchrone de promise2], heure=09:39:05:959, durée= 0 seconde(s) et 1 millisecondes
[fin du code principal du script], heure=09:39:05:960, durée= 0 seconde(s) et 13 millisecondes
[fin fonction asynchrone de promise1], heure=09:39:06:977, durée= 1 seconde(s) et 19 millisecondes
[promise1.then], heure=09:39:06:980, durée= 1 seconde(s) et 21 millisecondes, result=[réussite]
[promise1.finally] heure=09:39:06:982, durée= 1 seconde(s) et 24 millisecondes
[fin fonction asynchrone de promise2], heure=09:39:07:976, durée= 2 seconde(s) et 17 millisecondes
[promise2.catch], heure=09:39:07:978, durée= 2 seconde(s) et 19 millisecondes, result=[échec]
[promise2.finally], heure=09:39:07:980, durée= 2 seconde(s) et 21 millisecondes
[Done] exited with code=0 in 2.589 seconds
Açıklamalar
- 3. satır: [promise1]'in asenkron işlevi başlatılır, ancak bir olayla bildirilecek olan bitimini beklemez;
- 4. satır: [promise2]'in asenkron işlevi başlatılır, ancak bir olayla bildirilecek olan bitimini beklemez;
- satır 5: ana kodun sonu ve olay döngüsüne geri dönüş;
- 6. satır: [fin fonction asynchrone de promise1] olayının işlenmesi. [promise1]'in durumu [resolved]'e geçecek. Bir olay bunu bildirir;
- satır 7: [promise2] işlemi henüz tamamlanmadığından, döngüye yeni eklenen [promise1 resolved] olayı, önce [promise1.then] yöntemi, ardından [promise.finally] yöntemi tarafından işlenecektir (satır 8);
- 9-11. satırlar: [promise2], [pending] durumundan [resolved] durumuna geçtiğinde de aynı mekanizma devreye girer;
11.5. [async-05] komut dosyası
[Promise] nesnesinin oluşturucu koduna geri dönelim:
- satırda, [Promise] nesnesinin asenkron görevi başlatılır. Bu görev, genellikle onu kapsayan fonksiyonun ilettiği [resolve, reject] parametrelerinden daha fazlasına ihtiyaç duyar. Bu durumda, [Promise]'in oluşturulmasını, asenkron işlevinin ihtiyaç duyduğu parametreleri ona aktaracak bir işlev içine alınır:
Aşağıdaki komut dosyası:
- bir [Promise] döndüren iki asenkron işlev tanımlar;
- bu işlevlerin paralel olarak çalıştırılmasını sağlar ve belirli bir işlemi gerçekleştirmek için her ikisinin de tamamlanmasını bekler;
'use strict';
// [Promise] türünde değer döndüren asenkron işlevler tanımlanabilir
// bu fonksiyonlar daha sonra [async] anahtar sözcüğüyle etiketlenebilir
// ithalatları
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// başlangıcı
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// bir söz veren asenkron işlev [Promise]
function async01(p1) {
return new Promise((resolve) => {
console.log("[début de la tâche asynchrone async01]");
// asenkron görev
const débutAsync01 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
// asenkron görev karmaşık bir sonuç döndürebilir
resolve({
prop1: [10, 20, 30],
prop2: "abcd",
prop3: p1,
});
}, 1000)
});
}
// bir söz veren asenkron işlev [Promise]
function async02(p1, p2) {
return new Promise(resolve => {
console.log("[début de la tâche asynchrone async02]");
// asenkron görev
const débutAsync02 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
// asenkron görev karmaşık bir sonuç verebilir
resolve({
prop1: [11, 21, 31],
prop2: "xyzt",
prop3: p1 + p2
});
}, 2000)
})
}
// iki asenkron işlev de paralel olarak başlatılır
// ve her ikisinin de tamamlanmasını bekliyoruz
// "then" bloğu, ancak her iki işlev de [resolved] olayını tetiklediğinde çalışır
// catch bloğu, iki işlevden herhangi biri [rejected] olayını tetiklediği anda çalışır
Promise.all([async01(10), async02(10, 20)])
// olayını yayınladığında çalışır; sonuç, [result1, result2] adlı bir dizidir; burada [result1], [async01]
// ve [result2], [async02]'in bir [resolve]'i tarafından üretilen sonuçtur
.then(result => {
console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
})
// hatası, iki asenkron işlevden birinin ilk [reject]'i tarafından üretilen sonuçtur
.catch(error => {
console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
})
// finally, then veya catch'ten sonra yürütülür
.finally(() => {
console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
});
// asenkron işlevlerin ve ilgili olayların mesajlarından önce görüntülenecektir
console.log("[fin du code principal du script],", heure(débutScript));
// yardımcı program
function heure(début) {
// geçerli saat
const now = moment(Date.now());
// saat biçimlendirme
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// süre biçimlendirme
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// sonuç
return result;
}
Açıklamalar
- 15-30. satırlar: Bir zamanlayıcı olayı aracılığıyla 1 saniye sonra sonucunu döndüren [async01] işlevi tanımlanır. [async01] işlevi, 26. satırda bu işlevin sonucunu kullanır;
- 33-47. satırlar: Aynı işlem, bir zamanlayıcı olayı aracılığıyla 2 saniye sonra sonucunu veren [async02] işlevi için de yapılır. [async02] işlevi, 44. satırdaki sonuçta kullanılan iki parametre kabul eder;
- [async01, async02] adlı iki fonksiyon çağrıldığında:
- çalıştırılır;
- çağıran koda iki [promise1, promise2] sözü verecek;
- ardından yürütme, işine devam edecek olan çağıran koda geri dönecektir;
- yaklaşık 1 saniye sonra [async01], işini tamamladığını bildiren bir olay yayınlayacaktır. Söz konusu olay, [async01] tarafından olayla birlikte iletilen sonuca ilişkin olay döngüsünde beklemeye alınacaktır;
- yaklaşık 2 saniye sonra, aynı süreç [async02] için de tekrarlanacaktır;
- 54. satır: [async01, async02] asenkron işlevleri ancak bu aşamada yürütülür (async01(10) ve async02(10,20) notasyonları). Bu işlevler, [Promise.all] yöntemine parametre olarak aktarılan bir dizi içinde yürütülür. [async01, async02] ve [Promise.all]'in her ikisinin de çağıran koda birer söz verdiği bilinmektedir. Dolayısıyla, [Promise.all]'in parametresi iki sözten oluşan bir dizidir;
- [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] bir olay aboneliğidir:
- [Promise.all], [Promise] türündedir;
- [then] yönteminin [f1] işlevi, [all] yönteminin parametre dizisindeki tüm [promise1, promise2, …, promisen] sözleri[pending] durumundan [resolved] durumuna geçtiğinde yürütülecektir. Başka bir deyişle, [f1], dizideki tüm sözler başarıyla tamamlandığında yürütülecektir;
- [catch] yöntemine ait [f2] işlevi, dizideki vaatlerden biri [pending] durumundan [rejected] durumuna geçer geçmez çalıştırılacaktır. Başka bir deyişle, tablodaki vaatlerden biri başarısız olur olmaz [f2] yürütülür;
- [finally] yöntemine ait [f3] işlevi, [then, catch] yöntemlerinden birinin yürütülmesinden sonra çalıştırılacaktır, dolayısıyla her zaman çalıştırılacaktır;
Kodun çalıştırılması aşağıdaki sonuçları verir:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-05.js"
[début du script], heure=12:17:17:367
[début de la tâche asynchrone async01]
[début de la tâche asynchrone async02]
[fin du code principal du script], heure=12:17:17:375, durée= 0 seconde(s) et 10 millisecondes
[fin de la tâche asynchrone async01], heure=12:17:18:391, durée= 1 seconde(s) et 17 millisecondes
[fin de la tâche asynchrone async02], heure=12:17:19:389, durée= 2 seconde(s) et 14 millisecondes
[promise-all success], heure=12:17:19:390, durée= 2 seconde(s) et 25 millisecondes, result=[{"prop1":[10,20,30],"prop2":"abcd","prop3":10},{"prop1":[11,21,31],"prop2":"xyzt","prop3":30}]
[promise-all finally], heure=12:17:19:392, durée= 2 seconde(s) et 27 millisecondes
[Done] exited with code=0 in 2.572 seconds
- 6-7. satırlar: iki asenkron görev [async01, async02] başlatılır. Bunlar paralel olarak çalışır. Kodlarının yürütülmesi paralel olarak yapılmaz, ancak talep edilen veriyi bekleme süreleri aynı anda gerçekleşir;
- 5. satır: Komut dosyasının ana kodu tamamlanmıştır. Geriye, iki asenkron görev olan [async01, async02]'in tamamlanmasını beklemek kalır;
- 6. satır: [async01] adlı asenkron görev, başlatılmasından yaklaşık 1 saniye sonra sona erer. [resolve] işleviyle bir sonuç döndürür; dolayısıyla kodun 56. satırındaki tabloda yer alan söz konusu söz, [pending] durumundan [resolved] durumuna geçer. Bu, kodun 59-60. satırlarında yer alan [then] yöntemini tetiklemek için yeterli değildir;
- 7. satır: [async02] adlı asenkron görev, başlatılmasından yaklaşık 2 saniye sonra sona eriyor. [resolve] işleviyle bir sonuç döndürdüğünden, kodun 56. satırındaki tabloda yer alan söz konusu söz, [pending] durumundan [resolved] durumuna geçer. [then] yöntemi, olay döngüsü izin verir vermez çalıştırılacaktır;
- 8. satır: [Promise.all]'in [then] yöntemi yürütülür. Bu yöntem, parametre olarak [result1, result2] dizisini alır; burada [result1], [async01] tarafından üretilen sonuçtur, ve [result2] ise [async02] tarafından üretilen sonuçtur;
- 9. satır: [Promise.all]'in [finally] yöntemi yürütülür;
11.6. [async-06] komut dosyası
Bu yeni komut dosyası, [async / await] anahtar kelimelerinin birlikte kullanılmasıyla senkron koda benzeyen asenkron bir kodun nasıl elde edilebileceğini göstermektedir. Olay yönetimi tamamen gizlenmiştir ve kodun anlaşılması kolaylaştırılmıştır.
Önceki örneği ele alalım ve şu değişiklikleri yapalım:
- üçüncü bir asenkron işlev [async03] ekleniyor; bu işlev, sonucunu [Promise.reject] yöntemiyle geri döndürüyor ve böylece olay döngüsüne işini "başaramadığını" bildiriyor;
- Üç asenkron fonksiyonu [async01, async02, async03] sırayla çalıştırıyoruz. Önceki örnekte, [async01, async02] asenkron fonksiyonlarını paralel olarak çalıştırmıştık;
- [async/await] anahtar kelimeleri ortaya çıkmadan önce, asenkron eylemlerin sıralı yürütülmesi, birbirinin içine yerleştirilmiş [Promise] işlevleri yardımıyla gerçekleştiriliyordu. Bu şekilde yürütülmesi gereken birden fazla asenkron eylem olduğunda, sözcük sayısı buna bağlı olarak artar ve kod daha az okunabilir hale gelirdi;
- [async/await] anahtar kelimeleriyle, asenkron görevlerin sıralı yürütülmesi, senkron görevlerin yürütülmesine benzer bir sözdizimi ile gerçekleştirilir:
// asenkron işlev - async / await kullanımı
async function main() {
// asenkron görevlerin sıralı yürütülmesi
try {
// [async01]'i bekleyerek yürütme
const result1 = await async01(...);
console.log("[async01 result]=", result1);
// [async02]'i bekleyerek yürütme
const result2 = await async02(...);
console.log("[async02 result]=", result2);
// [async03]'i bekleyerek yürütme
const result3 = await async03(...);
console.log("[async03 result]=", result3);
} catch (error) {
// asenkron işlemlerden biri başarısız oldu
console.log(sprintf("[sequential error]= %j, %s", error));
} finally {
// tamamlandı
console.log("[fin exécution séquentielle des tâches asynchrones],");
}
- 6. satır: [async01] asenkron işlevi başlatılır (await anahtar sözcüğü) ve işlevin [Promise.resolve, Promise.reject] yöntemlerinden biri aracılığıyla sonucunu yayınlaması beklenir. Dolayısıyla bu, engelleyici bir işlemdir;
- 6. satır: [await] anahtar sözcüğü, [async01] asenkron işlemini engelleyici bir işleme dönüştürür. [async01] işleminin iki şekilde sonuç döndürdüğü bilinmektedir:
- çağıran koda, neredeyse anında bir [Promise] nesnesi döndürür;
- daha sonra [Promise.resolve, Promise.reject] yöntemleri aracılığıyla olay döngüsüne bir sonuç yayınlar. 6. satırdaki [result1], işte bu son sonucu alır. [async01] eyleminin olay yönetimi görünmez hale gelmiştir;
- eğer [async01]’in sonucu olan [result], [Promise.resolve(result)] tarafından yayınlanırsa, bu sonuç 6. satırdaki [result1]’e atanır ve yürütme 7. satırda devam eder;
- [async01]’in sonucu [Promise.reject] tarafından yayınlanırsa, bu bir istisna oluşturur ve kodun yürütülmesi 14. satıra, yani catch bloğuna geçer. [catch] cümlesinin parametresi, [async01] tarafından [Promise.reject(error)] ifadesiyle yayınlanan hata nesnesidir. Asenkron görev, hatayı bir [throw(error)] aracılığıyla da yayınlayabilir. [error] nesnesi, [catch(error)]'te alınan nesnedir;
- [await] anahtar sözcüğü, 2. satırda [async] anahtar sözcüğünün öncülünde yer alan bir işlev içinde zorunlu olarak bulunmalıdır. Bu anahtar sözcük, [main] işlevinin asenkron bir işlev olduğunu belirtir;
- [await f(…)] ifadesinde, [f], çağıran koda bir [Promise] nesnesi döndüren asenkron bir işlev olmalıdır;
- aynı işlem, 9. satırdaki [async02] ve 12. satırdaki [async03] asenkron eylemleri için de tekrarlanır;
Yine [async / await] anahtar kelimeleriyle, aşağıdaki sözdizimini kullanarak asenkron görevlerin paralel olarak yürütülmesini sağlayabilirsiniz:
try {
// asenkron görevlerin paralel yürütülmesi
const result = await Promise.all([async01(...), async02(...), async03(...)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// asenkron işlemlerden biri başarısız oldu
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// tamamlandı
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
- 3. satır: burada bir engelleyici işlem vardır: [async01(..), async02(..), async03(..)] tablosundaki üç asenkron görevin, [Promise.resolve, Promise.reject] yöntemlerinden birini kullanarak sonuçlarını olay döngüsüne yayınlamasını bekleriz;
- eğer üç asenkron görev sonuçlarını [Promise.resolve] ile yayınlarsa, [result] sabiti, [result1, result2, result3] dizisidir; burada:
- [result1], [async01] tarafından [Promise.resolve(result1)] ifadesiyle yayınlanan sonuçtur;
- [result2], [async02] tarafından [Promise.resolve(result2)] ifadesiyle yayınlanan sonuçtur;
- [result3], [async03] tarafından [Promise.resolve(result3)] ifadesiyle yayınlanan sonuçtur;
- üç görevden herhangi biri sonucunu [Promise.reject(error)] ifadesiyle yayınlarsa bir istisna oluşur;
- 3. satırdaki [result] sabiti değer almaz;
- yürütme doğrudan 5. satırdaki [catch] ifadesine geçer;
- catch bloğundaki (error) parametresi, [Promise.reject(error)] ifadesi tarafından yayınlanan (error) nesnesidir;
Bu iki sözdizimini birleştirerek, asenkron görevleri sıralı veya paralel olarak yürütmek mümkündür; tüm bunlar, senkron kodunkine benzer bir sözdizimi ile gerçekleştirilir. Bu nedenle, öncekilerden çok daha okunaklı olan bu sözdizimini tercih etmek gerekir. Bu [async / await] sözdizimi, ECMAScript sürüm 6'dan itibaren kullanılabilir. Hâlâ [Promise] vaatlerini kullanan birçok JavaScript kodu bulunmaktadır. Bu nedenle, bunların nasıl çalıştığını da anlamak önemlidir.
[async-06] betiğinin tam kodu şöyledir:
''use strict';
// birden fazla asenkron görevin paralel veya sıralı yürütülmesi
// async / await anahtar sözcükleriyle
// ithalatlar
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// başlangıç
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());
// bir [Promise] döndüren asenkron işlev
function async01(débutAsync01) {
return new Promise(function (resolve) {
console.log("[début fonction asynchrone async01],", heure());
// asenkron işlev
setTimeout(function () {
console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
// asenkron işlem karmaşık bir sonuç verebilir
// burada başarılı
resolve({
prop1: [11, 21, 31],
prop2: "abcd"
});
}, 1000)
});
}
// bir asenkron işlev, [Promise] sonucunu verir
function async02(débutAsync02) {
console.log("[début fonction asynchrone async02],", heure());
return new Promise(function (resolve) {
// asenkron işlev
setTimeout(function () {
console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
// asenkron işlem, karmaşık bir sonuç verebilir
// burada başarılı
resolve({
prop1: [12, 22, 32],
prop2: "xyzt"
});
}, 2000)
})
}
// bir asenkron işlev, [Promise] sonucunu verir
function async03(débutAsync03) {
console.log("[début fonction asynchrone async03],", heure());
return new Promise((resolve, reject) => {
// asenkron işlev
setTimeout(function () {
console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
// asenkron işlem, karmaşık bir sonuç verebilir
// burada hata
reject({
prop1: [13, 23, 33],
prop2: "échec"
});
}, 3000)
})
}
// asenkron işlev - async / await kullanımı
async function main() {
const débutSequential = moment(Date.now());
// asenkron görevlerin sıralı yürütülmesi
console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
try {
// [async01]'i bekleyerek yürütme
const débutAsync01 = moment(Date.now());
const result1 = await async01(débutAsync01);
console.log("[async01 result]=", result1);
// [async02]'i bekleyerek yürütme
const débutAsync02 = moment(Date.now());
console.log("début async02-------------", heure());
const result2 = await async02(débutAsync02);
console.log("[async02 result]=", result2);
// [async03]'i bekleyerek yürütme
const débutAsync03 = moment(Date.now());
console.log("début async03-------------", heure());
const result3 = await async03(débutAsync03);
console.log("[async03 result]=", result3);
} catch (error) {
// asenkron işlemlerden biri başarısız oldu
console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
} finally {
// tamamlandı
console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
}
const débutParallel = moment(Date.now());
// asenkron görevlerin paralel olarak yürütülmesi
console.log("------------ exécution parallèle des tâches asynchrones lancée ------------------------")
try {
const result = await Promise.all([async01(débutParallel), async02(débutParallel), async03(débutParallel)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// asenkron işlemlerden biri başarısız oldu
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// tamamlandı
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
// tamamlandı
console.log("[fin de la fonction main],", heure(débutSequential));
}
// asenkron main işlevi yürütülüyor
main();
// asenkron işlevlerin ve bunların olaylarının çeşitli mesajlarından önce görüntülenecektir
console.log("[fin du code principal du script],", heure(débutScript));
// yardımcı program
function heure(début) {
// geçerli saat
const now = moment(Date.now());
// saat biçimlendirme
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// süre biçimlendirme
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// sonuç
return result;
}
Çalıştırma sonuçları şöyledir:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-06.js"
[début du code principal du script], heure=15:02:00:152
------------ exécution séquentielle des tâches asynchrones lancée ------------------------
[début fonction asynchrone async01], heure=15:02:00:161
[fin du code principal du script], heure=15:02:00:164, durée= 0 seconde(s) et 15 millisecondes
[fin fonction asynchrone async01], heure=15:02:01:165, durée= 1 seconde(s) et 4 millisecondes
[async01 result]= { prop1: [ 11, 21, 31 ], prop2: 'abcd' }
début async02------------- heure=15:02:01:253
[début fonction asynchrone async02], heure=15:02:01:254
[fin fonction asynchrone async02], heure=15:02:03:265, durée= 2 seconde(s) et 12 millisecondes
[async02 result]= { prop1: [ 12, 22, 32 ], prop2: 'xyzt' }
début async03------------- heure=15:02:03:268
[début fonction asynchrone async03], heure=15:02:03:268
[fin fonction asynchrone async03], heure=15:02:06:285, durée= 3 seconde(s) et 18 millisecondes
[sequential error]= {"prop1":[13,23,33],"prop2":"échec"}, heure=15:02:06:289, durée= 6 seconde(s) et 129 millisecondes
[fin exécution séquentielle des tâches asynchrones], heure=15:02:06:291, durée= 6 seconde(s) et 131 millisecondes
------------ exécution parallèle des tâches asynchrones lancée ------------------------
[début fonction asynchrone async01], heure=15:02:06:292
[début fonction asynchrone async02], heure=15:02:06:293
[début fonction asynchrone async03], heure=15:02:06:294
[fin fonction asynchrone async01], heure=15:02:07:294, durée= 1 seconde(s) et 2 millisecondes
[fin fonction asynchrone async02], heure=15:02:08:298, durée= 2 seconde(s) et 6 millisecondes
[fin fonction asynchrone async03], heure=15:02:09:297, durée= 3 seconde(s) et 5 millisecondes
[parallel error], heure=15:02:09:298, durée= 3 seconde(s) et 6 millisecondes, erreur={"prop1":[13,23,33],"prop2":"échec"}
[fin exécution parallèle des tâches asynchrones],heure=15:02:09:299, durée= 3 seconde(s) et 7 millisecondes
[fin de la fonction main], heure=15:02:09:300, durée= 9 seconde(s) et 140 millisecondes
[Done] exited with code=0 in 9.668 seconds