Ders 07 / 17
async/await
async fonksiyonun her zaman söz döndürmesi, await'in fonksiyonu askıya alması, sözdizimsel katmanın söz zincirine karşılığı ve farkında olmadan sıralı beklemeye düşme hatası.
İçindekiler
Sözler eşzamansız işlemi bir değere dönüştürdü, birleştiriciler de beklemeyi düzenledi. Buna karşın kodun okunuşu değişmedi: her adımın sonucu hâlâ bir geri çağrının içinde işleniyor ve akış, kaynak metinde yukarıdan aşağı akmıyor.
Bu ders, aynı söz zincirlerini sıralı bir yazımla ifade eden katmanı kurar. Vurgulanacak
nokta baştan söylenmelidir: async ve await yeni bir eşzamansızlık düzeneği getirmez.
İkisi de sözler üzerine kurulmuş sözdizimsel bir katmandır; olay döngüsü kuralları
aynen geçerlidir.
async Her Zaman Söz Döndürür
async anahtar sözcüğüyle tanımlanan bir fonksiyon, ne döndürürse döndürsün bir söz
döndürür. Sıradan bir değer döndürürse, söz o değerle yerine gelir.
async function sabitOlcum() { return { istasyon: "A1", deger: 21.4 }; } const donen = sabitOlcum(); console.log("dönen değer bir söz mü:", donen instanceof Promise); console.log("beklenmiş sonuç:", await donen);
dönen değer bir söz mü: true
beklenmiş sonuç: { istasyon: 'A1', deger: 21.4 }
Bunun tersi de doğrudur: async fonksiyonun gövdesinde atılan bir hata, dışarıya
fırlamaz; dönen sözü reddeder. Hata yayılımının ayrıntısı sonraki dersin konusudur.
await Fonksiyonu Askıya Alır, İş Parçacığını Değil
await bir sözün sonuçlanmasını bekler. Beklerken yalnız içinde bulunduğu fonksiyonu
askıya alır; iş parçacığı serbest kalır ve çağıran koda geri döner.
Bunun gözlenebilir sonucu şudur: async bir fonksiyonun gövdesi, ilk await ifadesine
kadar eşzamanlı çalışır; oradan sonrası bir mikro göreve dönüşür.
async function olcumIsle() { console.log("2 — async gövde eşzamanlı başlar"); await null; console.log("4 — ilk await sonrası mikro görevde sürer"); } console.log("1 — çağrıdan önce"); olcumIsle(); console.log("3 — çağrıdan sonra");
1 — çağrıdan önce 2 — async gövde eşzamanlı başlar 3 — çağrıdan sonra 4 — ilk await sonrası mikro görevde sürer
Beklenen değer bir söz bile değildi; await null da bir mikro görev turu ekler. Kural
biçimsel olarak şudur: await ifadesi, beklenen değeri Promise.resolve ile sarar ve
devamını o sözün geri çağrısı olarak kaydeder.
İki async fonksiyon iç içe değil, yan yana ilerleyebilir. Her await, denetimi olay
döngüsüne bırakır ve diğer fonksiyonun sıradaki adımı araya girer:
async function ilkAkis() { console.log("A — adım 1"); await null; console.log("A — adım 2"); await null; console.log("A — adım 3"); } async function ikinciAkis() { console.log("B — adım 1"); await null; console.log("B — adım 2"); await null; console.log("B — adım 3"); } ilkAkis(); ikinciAkis(); console.log("eşzamanlı kısım bitti");
A — adım 1 B — adım 1 eşzamanlı kısım bitti A — adım 2 B — adım 2 A — adım 3 B — adım 3
İki akış adım adım birbirine geçti; buna karşın hiçbir an ikisi aynı anda çalışmadı. Tek
iş parçacıklı model bozulmadı — yalnız bölünme noktaları await ifadeleriyle açıkça
işaretlendi.
Zincirin Karşılığı
Aynı akış iki yazımla da ifade edilebilir; ürettikleri söz ve sonuç aynıdır.
const KAYNAK = { A1: { gecikme: 30, deger: 21.4 }, B2: { gecikme: 10, deger: 19.8 }, C3: { gecikme: 20, deger: 23.1 }, D4: { gecikme: 15, hata: "sensör arızası" }, }; function olcumGetir(istasyon) { console.log("istek başladı:", istasyon); return new Promise((karar, red) => { const kayit = KAYNAK[istasyon]; setTimeout(() => { if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`)); else karar({ istasyon, deger: kayit.deger }); }, kayit.gecikme); }); } async function ortalamaAl() { const a = await olcumGetir("A1"); const b = await olcumGetir("B2"); return (a.deger + b.deger) / 2; } function ortalamaAlZincir() { return olcumGetir("A1").then((a) => olcumGetir("B2").then((b) => (a.deger + b.deger) / 2), ); } console.log("await yazımı:", (await ortalamaAl()).toFixed(2)); console.log("zincir yazımı:", (await ortalamaAlZincir()).toFixed(2));
istek başladı: A1 istek başladı: B2 await yazımı: 20.60 istek başladı: A1 istek başladı: B2 zincir yazımı: 20.60
İki yazımın farkı okunabilirliktedir. await yazımında ara sonuçlar sıradan yerel
değişkenlerdir; ikinci adımda birinci adımın sonucuna erişmek için iç içe geçmek
gerekmez. Adım sayısı arttıkça fark büyür: zincir yazımında her ek adım ya yeni bir
girinti ya da elle taşınan bir ara nesne ister.
Sıralı Bekleme Tuzağı
await sıralı bir okunuş verir ve bu okunuş yanıltıcı olabilir: art arda yazılmış
beklemeler gerçekten art arda yürür.
const KAYNAK = { A1: { gecikme: 30, deger: 21.4 }, B2: { gecikme: 10, deger: 19.8 }, C3: { gecikme: 20, deger: 23.1 }, D4: { gecikme: 15, hata: "sensör arızası" }, }; function olcumGetir(istasyon) { console.log("istek başladı:", istasyon); return new Promise((karar, red) => { const kayit = KAYNAK[istasyon]; setTimeout(() => { if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`)); else karar({ istasyon, deger: kayit.deger }); }, kayit.gecikme); }); } for (const istasyon of ["A1", "B2", "C3"]) { const olcum = await olcumGetir(istasyon); console.log("geldi:", olcum.istasyon, olcum.deger); }
istek başladı: A1 geldi: A1 21.4 istek başladı: B2 geldi: B2 19.8 istek başladı: C3 geldi: C3 23.1
Çıktıdaki sıra tanıyı verir: her istek, bir öncekinin sonucu geldikten sonra başlıyor. Üç istasyon birbirinden bağımsız olduğu halde beklemeler üst üste binmiyor; toplam süre üç gecikmenin toplamı kadar.
Düzeltme, isteklerin başlatılmasını beklemekten ayırmaktır. Sözler oluşturulduğunda iş başladığına göre, önce hepsi oluşturulur, sonra tek noktada beklenir.
const KAYNAK = { A1: { gecikme: 30, deger: 21.4 }, B2: { gecikme: 10, deger: 19.8 }, C3: { gecikme: 20, deger: 23.1 }, D4: { gecikme: 15, hata: "sensör arızası" }, }; function olcumGetir(istasyon) { console.log("istek başladı:", istasyon); return new Promise((karar, red) => { const kayit = KAYNAK[istasyon]; setTimeout(() => { if (kayit.hata) red(new Error(`${istasyon}: ${kayit.hata}`)); else karar({ istasyon, deger: kayit.deger }); }, kayit.gecikme); }); } const sozler = ["A1", "B2", "C3"].map((istasyon) => olcumGetir(istasyon).then((olcum) => { console.log("geldi:", olcum.istasyon, olcum.deger); return olcum; }), ); const olcumler = await Promise.all(sozler); console.log("toplam:", olcumler.reduce((t, o) => t + o.deger, 0).toFixed(1));
istek başladı: A1 istek başladı: B2 istek başladı: C3 geldi: B2 19.8 geldi: C3 23.1 geldi: A1 21.4 toplam: 64.3
Üç istek de hemen başladı; sonuçlar gecikme sırasına göre geldi. Toplam bekleme, üç gecikmenin toplamı değil en büyüğü kadar.
Ölçüt basit bir sorudur: bu adım, bir öncekinin sonucuna gerçekten muhtaç mı? Yanıt
hayırsa döngü içindeki await bir hatadır. Yanıt evetse — örneğin ikinci istasyonun
kimliği birinci ölçümden çıkıyorsa — sıralı bekleme doğrudur ve zorunludur.
Üst Düzey await
Modül düzeyinde, bir fonksiyonun içinde olmadan da await kullanılabilir. Bu dersteki
örneklerin son satırları bunu kullanır. Modül biçimlerine bağlı ayrıntılar Modüller,
Araçlar ve Ekosistem kursunun konusudur; burada bilinmesi gereken tek şey, üst düzey
beklemenin modülün değerlendirilmesini geciktirdiğidir.
Özet
asyncfonksiyon her zaman söz döndürür; gövdesinde atılan hata dönen sözü reddeder.awaityalnız kendi fonksiyonunu askıya alır; iş parçacığı serbest kalır ve çağıran koda dönülür.- Gövde ilk
awaitifadesine kadar eşzamanlı çalışır, sonrası mikro göreve dönüşür; söz olmayan bir değeri beklemek bile bir tur ekler. awaityazımı ile zincir yazımı aynı sözü üretir; fark okunabilirliktedir.- Bağımsız işler için döngü içinde
awaitkullanmak beklemeleri sıraya sokar; doğru kalıp, işleri başlatıp tek noktada beklemektir.
Sonraki Adım
Bu derste hata yolu bilinçli olarak dışarıda bırakıldı. Sonraki ders onu ele alır:
async gövdesinde atılan hatanın nereye gittiği, try/catch ile catch yazımının ne
zaman ayrıştığı, birleştiricilerin hangi durumda hata bilgisini kaybettiği ve hiç kimsenin
işlemediği bir reddetmenin çalışma zamanına nasıl bildirildiği. Ölçüm akışı da orada
arızalı istasyona dayanıklı hale gelecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.