İçeriğe geç
XDD.baby Yazılım Öğrenme
Sözlük Hata rehberi 0/56

Blok 2 · Ders 15

Asenkron JavaScript: Promise ve async/await

Beklemesi gereken işleri (zamanlayıcı, ağ isteği) programı durdurmadan yapmak: callback, Promise, async/await ve fetch.

Bu dersin sonunda

  • Senkron ve asenkron çalışmanın farkını açıklayıp setTimeout'lu bir programın çıktı sırasını olay döngüsüyle önceden söyleyebilirsin
  • new Promise ile kendi Promise'ini yazıp sonucunu then, catch ve finally ile karşılayabilirsin
  • async / await ile bekleyen kodu yukarıdan aşağı okunur biçimde yazıp hataları try / catch ile yakalayabilirsin
  • fetch ile internetten gerçek bir JSON çekip isteğin başarısını yanit.ok ile denetleyebilirsin
  • Promise.all ile birbirinden bağımsız işleri aynı anda bekleyip toplam süreyi kısaltabilirsin
  • Promise { <pending> }, yakalanmamış ret ve yanlış yerde await hatalarını mesajından tanıyıp düzeltebilirsin
Bu sayfada
  1. Beklemek zorunda olan programlar
  2. Olay döngüsü: sırayı kim belirliyor?
  3. Adım adım beklemek: callback cehennemi
  4. Promise: sonucun sözü
  5. async ve await: asenkron kodu düz yazmak
  6. fetch: internetten gerçek veri
  7. Promise.all: aynı anda beklemek
  8. Sık hatalar
  9. Görevler
  10. Kendini sına
  11. Kayıt noktası

Şimdiye kadar yazdığın programlarda, Ders 10'daki setTimeout dışında, her satır bir öncekinin bitmesini bekledi; zaten hiçbiri beklemeye değecek kadar uzun sürmüyordu. Gerçek programlar ise sık sık bir şey bekler: internetten gelecek bir veri, diskten okunacak bir dosya, birkaç saniye sonra çalacak bir zamanlayıcı.

Bu derste programı dondurmadan beklemeyi öğreneceksin. Önce JavaScript'in bekleyişleri nasıl sıraya koyduğunu (olay döngüsü), sonra bekleyen bir işin sonucunu almanın üç yolunu göreceksin: callback, Promise ve async / await. Dersin sonunda Riot'un sunucusundan gerçek bir JSON çekecek ve birden çok isteği aynı anda bekleyeceksin.

Bu derste C:\kod\ogren klasöründe yeni bir ders15.js dosyasıyla başlıyorsun; dosyanın en üstünde await kullanmaya başlayacağımız yerde ikinci bir dosya, ders15.mjs açacağız. Her yeni örnekten önce dosyadaki eski kodu sil ya da satırların başına // koy. Örneklerin çoğu bir iki saniye bekliyor: terminal hemen dönmezse telaşlanma, program bekleyen işi kalmayınca kendiliğinden biter.

Beklemek zorunda olan programlar

LoL istemcisinde Koleksiyon sekmesine tıkladığını düşün. İstemci kostümlerinin listesini Riot'un sunucusundan ister; cevap yarım saniyede, kötü bir günde birkaç saniyede gelir. İstemci bu sürede donup kalsaydı ne sohbete yazabilir ne de kuyruktan çıkabilirdin.

Beklemek gereken işler her yerde: internetten veri almak, dosya okumak, bir süre sonra bir şey yapmak. JavaScript bu sorunu şöyle çözer: uzun sürecek bir işi başlatır, sonucunu beklemeden sonraki satıra geçer; sonuç hazır olunca haber alır ve işin devamını o zaman yapar.

Ders 10'daki setTimeout tam olarak böyle davranıyordu. ders15.js dosyasını oluştur ve şunu yaz:

ders15.js
console.log("1. Kuyruğa girdin")setTimeout(() => {    console.log("2. Maç bulundu")}, 2000)console.log("3. Bu arada rünlerini düzenliyorsun")
Çıktı
1. Kuyruğa girdin3. Bu arada rünlerini düzenliyorsun2. Maç bulundu

setTimeout iki saniyelik beklemeyi başlatır ve hemen biter; program 5. satıra geçip 3'ü yazar. Süre dolunca verdiğin callback çağrılır ve 2 yazılır. Program bekleyen bir işi olduğu için iki saniye boyunca kapanmaz, iş bitince kendiliğinden biter.

Senkron olsaydı çıktı: 1, 2, 3 1 2 sn donar, hiçbir şey olmaz 2 3 setTimeout ile çıktı: 1, 3, 2 1 3 Node arka planda sayar 2 0 sn 1 sn 2 sn zaman
Şekil 15.1 Beklemeyi kendisi yapsaydı program iki saniye donardı (üstte). setTimeout beklemeyi Node'a bırakır, program yoluna devam eder (altta).

Bekleyen bir işin sonucunu almanın üç yolu var ve bu ders üçünü de sırayla öğretiyor: callback (en eski yol), Promise (bugünün temeli) ve async / await (bugün en çok yazılan). Üçünün altında da aynı düzen çalışıyor; önce onu tanı.

Olay döngüsü: sırayı kim belirliyor?

Ders 10'un sonundaki soruyu, harfler yerine sayılarla, bu kez kendin çalıştır. Süre sıfır milisaniye:

ders15.js
console.log("1")setTimeout(() => {    console.log("2")}, 0)console.log("3")
Çıktı
132

Sıfır milisaniye "hemen" demek değil mi? Değil; nedenini anlamak için JavaScript'in bekleyişleri nasıl düzenlediğine bakmak gerekiyor.

JavaScript aynı anda yalnızca tek bir iş yapar; bekleme gerektiren işleri kendisi beklemez, Node'a devreder. Bu düzenin üç parçası var:

  • Çağrı yığını (call stack): şu an çalışan kod ve onu çağıran fonksiyonlar, üst üste. Onu Ders 13'te tanıdın; yığın izi, hata anında bu yığının dökümüydü. JavaScript yalnızca yığının en üstündekini çalıştırır.
  • Node'un arka planı: zamanlayıcıları sayan, internetten gelecek cevabı bekleyen kısım. Bekleme burada olur, JavaScript'in elini meşgul etmez.
  • Görev kuyruğu (task queue): süresi dolan ya da cevabı gelen işlerin sıraya girdiği yer. İlk giren ilk çıkar.

Olay döngüsü (event loop) bu parçaları birbirine bağlayan kuraldır: "Yığın boş mu? Boşsa kuyruktaki ilk görevi al, yığına koy, çalıştır. Sonra yeniden bak." Program bitene kadar bu döngü döner.

Çağrı yığını şu an çalışan kod console.log("3") ders15.js ana kodu Node arka planda bekler zamanlayıcılar internet yanıtları Görev kuyruğu sırası gelen işler console.log("2") boş 1 2 3 1 setTimeout beklemeyi Node'a devreder ve hemen biter. 2 Süre dolunca Node, fonksiyonu görev kuyruğunun sonuna koyar. 3 Olay döngüsü: yığın boşalınca kuyruktaki ilk görevi yığına alır.
Şekil 15.2 setTimeout(…, 0) çalıştıktan hemen sonraki an: 2'yi yazacak fonksiyon kuyrukta hazır bekliyor, ama yığında hâlâ console.log("3") var.

Şimdi 1, 3, 2 çıktısını adım adım izle:

  1. console.log("1") yığına girer, çalışır, çıkar. Ekranda 1.
  2. setTimeout çalışır: fonksiyonu ve süreyi Node'a verir, hemen biter. Süre sıfır olduğu için Node fonksiyonu hemen görev kuyruğuna koyar.
  3. console.log("3") çalışır. Ekranda 3. Dosyanın sonuna gelindi, yığın boşaldı.
  4. Olay döngüsü yığının boş olduğunu görür ve kuyruktaki fonksiyonu yığına alır. Ekranda 2.

Kural: kuyruktaki bir iş, o anda çalışan kodun tamamı bitmeden başlamaz. setTimeout'a verdiğin süre "en erken bu kadar sonra" demektir, "tam bu kadar sonra" değil.

Adım adım beklemek: callback cehennemi

Bekleyen işler çoğu zaman birbirine bağlıdır: önce maç bulunmalı, sonra şampiyon seçilmeli, sonra yükleme ekranı bitmeli. "Bu iş bitince şunu yap" demenin ilk yolu, fonksiyona bir callback vermektir. Aşağıda her adım yarım saniye sürüyor ve bitince kendisine verilen sonra fonksiyonunu çağırıyor:

ders15.js
function macBul(sonra) {    setTimeout(() => {        console.log("Maç bulundu")        sonra()    }, 500)} function sampiyonSec(sonra) {    setTimeout(() => {        console.log("Ahri seçildi")        sonra()    }, 500)} function yuklemeEkrani(sonra) {    setTimeout(() => {        console.log("Yükleme ekranı bitti")        sonra()    }, 500)} macBul(() => {    sampiyonSec(() => {        yuklemeEkrani(() => {            console.log("Sihirdar Vadisi'ne hoş geldin")        })    })})
Çıktı
Maç bulunduAhri seçildiYükleme ekranı bittiSihirdar Vadisi'ne hoş geldin

Çalışıyor. Ama son yedi satıra bak: her adım bir öncekinin içine yazılıyor ve kod her adımda biraz daha sağa kayıyor. Üstelik gerçek hayatta her adım başarısız olabilir: maç bulunamaz, şampiyonu başkası kapar. Callback'li kodda bunun için her callback'e bir de hata parametresi eklenir ve her seviyede ayrı ayrı denetlenir:

Hata denetimli callback zinciri (yalnızca şekline bak, çalıştırma)
macBul((hata) => {    if (hata) {        console.log("Maç bulunamadı: " + hata.message)        return    }    sampiyonSec((hata, sampiyon) => {        if (hata) {            console.log("Seçim olmadı: " + hata.message)            return        }        yuklemeEkrani((hata) => {            if (hata) {                console.log("Yükleme olmadı: " + hata.message)                return            }            console.log(sampiyon + " ile Sihirdar Vadisi'ne hoş geldin")        })    })})

Üç adımda bile kodun şekli yana yatmış bir piramide döndü. On adımlı bir işte (giriş yap, profili al, maç geçmişini al, her maçın ayrıntısını al…) piramit ekrandan taşar; hangi }) hangi adımı kapatıyor bulmak zorlaşır, araya bir adım eklemek bütün yapıyı bozar. Bu duruma callback cehennemi denir.

Promise: sonucun sözü

Callback'te "iş bitince bunu çağır" diye işe bir fonksiyon verirdin. Promise'te tersi olur: asenkron iş sana hemen bir obje verir. Bu obje "sonuç şu an yok ama gelecek; gelince haber vereceğim" diyen bir sözdür. Sözü bir değişkene koyabilir, başka bir fonksiyona verebilir, sonuç gelince ne yapılacağını sonradan söyleyebilirsin.

bekliyor pending davet gönderildi tamamlandı fulfilled değeri then'e gider reddedildi rejected hatası catch'e gider resolve(değer) reject(hata) Sonuçlanan Promise bir daha değişmez. finally iki durumda da çalışır.
Şekil 15.3 Promise'in üç durumu. Bekleyen bir Promise ya resolve ile tamamlanır ya da reject ile reddedilir; ikisi birden olmaz.

Promise oluşturmak: new Promise ve resolve

Bir Promise'i new Promise(...) ile oluşturursun. new sözcüğünü Ders 13'te new Error(...) yazarken gördün; tam olarak ne yaptığını Ders 16'da öğreneceksin, şimdilik "yeni bir Promise oluştur" diye oku.

Parantezin içine bir fonksiyon verirsin. JavaScript bu fonksiyonu hemen çalıştırır ve ona parametre olarak başka bir fonksiyon uzatır. Parametrenin adını sen koyarsın, ama herkes resolve (İngilizcede "sonuçlandır") der. İş bitince resolve(değer) çağırırsın; Promise tamamlanır ve o değeri taşır.

ders15.js
const davet = new Promise((resolve) => {    setTimeout(() => {        resolve("Lux daveti kabul etti")    }, 1000)}) console.log(davet)davet.then((cevap) => {    console.log(cevap)    console.log(davet)})
Çıktı
Promise { <pending> }Lux daveti kabul ettiPromise { 'Lux daveti kabul etti' }
  • console.log(davet) hemen çalıştı. Bir saniye henüz dolmadığı için Promise bekliyor; Node bunu Promise { <pending> } diye gösterir (pending: bekliyor). Bu yazıyı aklında tut, Sık hatalar bölümünde yeniden karşına çıkacak.
  • davet.then(fonksiyon) "Promise tamamlanınca bu fonksiyonu çağır ve değeri ona ver" demek. Then İngilizcede "sonra" demek.
  • Bir saniye sonra resolve("Lux daveti kabul etti") çağrıldı. Promise tamamlandı ve then'e verdiğin fonksiyon bu metni cevap parametresiyle aldı.
  • Son satırda Promise artık değerini taşıyor: Promise { 'Lux daveti kabul etti' }.

Reddetmek: reject, catch ve finally

Davet reddedilebilir de. new Promise'e verdiğin fonksiyon ikinci bir parametre alır: reject ("reddet"). reject(hata) çağırınca Promise reddedilir. Hata olarak Ders 13'teki gibi bir Error objesi vermek iyi bir alışkanlıktır: mesajı ve yığın izi de yanında gelir.

Aşağıdaki fonksiyon her çağrıldığında yeni bir davet (Promise) oluşturup return ile geri veriyor. Teemo her daveti reddediyor; kim bilir, belki mantar dikmekle meşgul:

ders15.js
function davetEt(oyuncu) {    return new Promise((resolve, reject) => {        setTimeout(() => {            if (oyuncu === "Teemo") {                reject(new Error(oyuncu + " daveti reddetti"))            } else {                resolve(oyuncu + " daveti kabul etti")            }        }, 1000)    })}

davetEt("Ahri") çağrısının sonucu bir Promise. Onu karşılamak için üç metot var. Fonksiyonun altına, 13. satırdan başlayarak yaz:

ders15.js
davetEt("Ahri")    .then((cevap) => {        console.log(cevap)    })    .catch((hata) => {        console.log("Olmadı: " + hata.message)    })    .finally(() => {        console.log("Davet penceresi kapandı")    })
Çıktı
Ahri daveti kabul ettiDavet penceresi kapandı

Şimdi 13. satırdaki "Ahri" yerine "Teemo" yaz ve yeniden çalıştır:

ders15.js
davetEt("Teemo")    .then((cevap) => {        console.log(cevap)    })    .catch((hata) => {        console.log("Olmadı: " + hata.message)    })    .finally(() => {        console.log("Davet penceresi kapandı")    })
Çıktı
Olmadı: Teemo daveti reddettiDavet penceresi kapandı
MetotNe zaman çalışır?Ders 13'teki karşılığı
then(fonksiyon)Promise tamamlanınca; fonksiyon değeri alırtry bloğunun devamı
catch(fonksiyon)Promise reddedilince; fonksiyon hatayı alırcatch (hata) { … }
finally(fonksiyon)Her iki durumda da, en sondafinally { … }

then, catch ve finally alt alta, başlarına nokta koyarak yazılır; aynı zincirin halkalarıdır. Her biri yeni bir Promise döndürdüğü için arkasına bir sonrakini ekleyebiliyorsun.

Zincirleme: cehennemden çıkış

Zincirin asıl gücü şu: then'e verdiğin fonksiyon bir Promise döndürürse, zincirdeki sonraki halka o Promise'in sonuçlanmasını bekler. Böylece iç içe callback'ler yerine alt alta halkalar yazarsın. Bunu denemek için önce verilen süre kadar bekleyen küçük bir yardımcı yaz, bekle. Callback cehennemindeki maç akışı onunla şöyle olur:

ders15.js
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} bekle(500)    .then(() => {        console.log("Maç bulundu")        return bekle(500)    })    .then(() => {        console.log("Ahri seçildi")        return bekle(500)    })    .then(() => {        console.log("Yükleme ekranı bitti")        console.log("Sihirdar Vadisi'ne hoş geldin")    })
Çıktı
Maç bulunduAhri seçildiYükleme ekranı bittiSihirdar Vadisi'ne hoş geldin

bekle yalnızca bekleyip tamamlanan bir Promise döndürüyor. setTimeout'a callback olarak doğrudan resolve'u verdik: süre dolunca resolve çağrılır, Promise tamamlanır. Kod artık sağa kaymıyor; her then bir sonraki bekleyişi return ile zincire veriyor. Bir adımda hata çıksaydı, zincirin sonuna koyacağın tek bir catch hepsini yakalardı.

Ret yakalanmazsa

Reddedilen bir Promise'in catch'i yoksa Node bunu "kimse ilgilenmedi" diye yorumlar ve programı çökertir. davetEt aynı; ama bu kez çağrıda catch yok ve programın sonunda iki saniyelik bir zamanlayıcı daha var:

ders15.js
function davetEt(oyuncu) {    return new Promise((resolve, reject) => {        setTimeout(() => {            if (oyuncu === "Teemo") {                reject(new Error(oyuncu + " daveti reddetti"))            } else {                resolve(oyuncu + " daveti kabul etti")            }        }, 1000)    })} davetEt("Teemo").then((cevap) => {    console.log(cevap)})setTimeout(() => {    console.log("Bu satır hiç çalışmayacak")}, 2000)
Hata çıktısı
C:\kod\ogren\ders15.js:5                reject(new Error(oyuncu + " daveti reddetti"))                       ^ Error: Teemo daveti reddetti    at Timeout._onTimeout (C:\kod\ogren\ders15.js:5:24)    ... Node.js v24.20.0

Hata, reject satırını (5. satır) gösteriyor. İki saniyelik zamanlayıcının mesajı hiç gelmedi: program, ret yakalanmadığı anda durdu. Bu, Ders 13'teki yakalanmamış throw ile aynı sonuç. Kural: her Promise zincirinin sonunda bir catch olsun (ya da birazdan göreceğin try / catch).

async ve await: asenkron kodu düz yazmak

Promise zinciri callback piramidinden çok daha düzenli. Ama her adım hâlâ bir fonksiyonun içinde; bir adımın sonucunu üç adım sonra kullanmak istersen değeri zincir boyunca taşımak zorlaşır. async ve await aynı Promise'leri kullanır ama kodu senkron kod gibi, yukarıdan aşağı yazmanı sağlar. İki kuralı var:

  • Bir fonksiyonun başına async (asynchronous, yani asenkron sözcüğünün kısaltması) yazarsan o fonksiyon her zaman bir Promise döndürür. return ile verdiğin değer o Promise'in değeri olur.
  • async bir fonksiyonun içinde bir Promise'in önüne await (İngilizcede "beklemek") yazarsan fonksiyon orada duraklar; Promise tamamlanınca değeriyle kaldığı yerden devam eder.
ders15.js
async function sampiyonGetir() {    return "Ahri"} console.log(sampiyonGetir())
Çıktı
Promise { 'Ahri' }

return "Ahri" yazdık ama gelen şey bir Promise; içinde 'Ahri' değeri var. Değeri almak için ya then kullanırsın ya da await. İkincisi daha okunaklı. Maç akışının üçüncü ve son hâli:

ders15.js
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function oyunaGir() {    await bekle(500)    console.log("Maç bulundu")    await bekle(500)    console.log("Ahri seçildi")    await bekle(500)    console.log("Yükleme ekranı bitti")    console.log("Sihirdar Vadisi'ne hoş geldin")} oyunaGir()console.log("Bu satır beklemeden çalışır")
Çıktı
Bu satır beklemeden çalışırMaç bulunduAhri seçildiYükleme ekranı bittiSihirdar Vadisi'ne hoş geldin

İki şeye dikkat et. Birincisi, kod düz bir fonksiyon gibi okunuyor: bekle, yaz, bekle, yaz. İkincisi, en alttaki console.log ilk sırada çıktı.

await yalnızca içinde bulunduğu fonksiyonu duraklatır, bütün programı değil: oyunaGir() ilk await'e gelince geriye bekleyen bir Promise bırakıp kenara çekilir, program da kaldığı yerden devam eder. Olay döngüsü yine iş başında.

Aynı maç akışını üç biçimde yazdın. Yan yana:

CallbackPromise zinciriasync / await
Adımlariç içealt alta then halkalarıalt alta satırlar
Hata yakalamaher seviyede if (hata)sonda tek catchtry / catch
Okumasısağa kayarhalka halkasenkron kod gibi

Hataları yakalamak: try / catch

await ettiğin Promise reddedilirse await satırı, tıpkı throw gibi bir hata fırlatır. Bu yüzden Ders 13'teki try / catch / finally olduğu gibi çalışır. davetEt'i de async ile yeniden yazalım. Artık new Promise yazmaya gerek yok: async fonksiyonda throw Promise'i reddeder, return tamamlar.

ders15.js
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function davetEt(oyuncu) {    await bekle(1000)    if (oyuncu === "Teemo") {        throw new Error(oyuncu + " daveti reddetti")    }    return oyuncu + " daveti kabul etti"} async function lobiKur() {    try {        const cevap = await davetEt("Teemo")        console.log(cevap)        console.log("Kuyruğa giriliyor")    } catch (hata) {        console.log("Olmadı: " + hata.message)    } finally {        console.log("Davet penceresi kapandı")    }} lobiKur()
Çıktı
Olmadı: Teemo daveti reddettiDavet penceresi kapandı

await davetEt("Teemo") satırı hata fırlattı. try'ın geri kalanı atlandı, catch çalıştı, en sonda finally. "Teemo" yerine "Ahri" yazarsan try sonuna kadar çalışır, catch atlanır, finally yine çalışır.

Sık hataSyntaxError: await is only valid in async functions and the top level bodies of modules

Ne zaman çıkar? await'i başında async olmayan bir fonksiyonun içinde kullanınca.

ders15.js
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} function canYenile() {    await bekle(1000)    console.log("Can yenilendi")} canYenile()
Hata çıktısı
C:\kod\ogren\ders15.js:8    await bekle(1000)    ^^^^^ SyntaxError: await is only valid in async functions and the top level bodies of modules    at wrapSafe (node:internal/modules/cjs/loader:1861:18)    ... Node.js v24.20.0

Mesajın Türkçesi: "await yalnızca async fonksiyonlarda ve modüllerin en üst düzeyinde geçerlidir." ^^^^^ işaretleri await'in altında. Bu bir SyntaxError olduğu için program hiç başlamadı (Ders 1); 12. satırdaki çağrıya sıra bile gelmedi.

Nasıl düzelir? await'in bulunduğu fonksiyonun başına async ekle: async function canYenile() {. Mesajın ikinci yarısındaki "modüllerin en üst düzeyi" ise bir sonraki konu.

En üst düzeyde await: yalnız ES modülünde

Şimdiye kadar await'i hep bir async fonksiyonun içinde yazdık. Dosyanın en üstünde, hiçbir fonksiyonun içinde değilken yazmak mümkün mü? Evet, ama yalnızca ES modülünde.

Ders 14'ten hatırla: Node bir dosyayı iki sistemden biriyle çalıştırır. import / export kullanan yeni sistem ES modülü, require kullanan eski sistem CommonJS. Adı .mjs ile biten dosya her zaman ES modülü, .cjs ile biten her zaman CommonJS sayılır; .js dosyalarında kararı package.json'daki type satırı verir. ders15.mjs adında yeni bir dosya aç ve şunu yaz:

ders15.mjs — ogren — Visual Studio Code
ders15.mjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} console.log("Geri dönüş başladı")await bekle(1000)console.log("Üsse döndün")
PROBLEMSOUTPUTDEBUG CONSOLETERMINAL
PS C:\kod\ogren> node ders15.mjsGeri dönüş başladıÜsse döndün
Ln 1, Col 1Spaces: 4UTF-8JavaScript

Dosyanın adı .mjs ile bitiyor; Node onu ES modülü olarak çalıştırır ve 8. satırdaki en üst düzey await sorunsuz çalışır. Bu dersin geri kalanında en üstte await kullanan örnekler bu dosyada.

Şimdi bilerek hata yap. Aynı kodu File → Save As… ile ders15.cjs adıyla da kaydet ve çalıştır. .cjs uzantısı Node'a "bu dosya kesinlikle CommonJS" der. Kaydettikten sonra düzenleyicide ders15.cjs açık kalır; ders15.mjs olduğu gibi durur.

Sık hataEn üstte await: CommonJS dosyasında SyntaxError

Ne zaman çıkar? CommonJS sayılan bir dosyanın en üstünde (hiçbir fonksiyonun içinde değilken) await kullanınca.

ders15.cjs — ogren — Visual Studio Code
ders15.cjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} console.log("Geri dönüş başladı")await bekle(1000)console.log("Üsse döndün")
Ln 1, Col 1Spaces: 4UTF-8JavaScript

Kod ders15.mjs ile aynı; yalnızca dosyanın uzantısı farklı. VS Code 8. satırın altını çizmiyor (bir önceki karttaki async olmayan fonksiyonda çizmişti); bu hatayı ancak çalıştırınca görürsün.

Terminal — PowerShell
PS C:\kod\ogren> node ders15.cjs(node:33392) Warning: Failed to load the ES module: C:\kod\ogren\ders15.cjs. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.(Use `node --trace-warnings ...` to show where the warning was created)C:\kod\ogren\ders15.cjs:8await bekle(1000)^^^^^ SyntaxError: await is only valid in async functions and the top level bodies of modules    at wrapSafe (node:internal/modules/cjs/loader:1861:18)    ... Node.js v24.20.0

Uzun satırları görmek için terminal kutusunu yana kaydırabilirsin. (node:33392) içindeki sayı, Windows'un o an çalışan programa verdiği numara; sende başka bir sayı olur.

Alttaki SyntaxError bir önceki karttaki hatanın aynısı: CommonJS dosyası "modül" sayılmadığı için en üst düzeyde await yasak. Üstteki uyarıyı (Warning) Ders 14'te import satırında görmüştün; bu kez sebep en üstteki await. Uyarının sonu iki çözüm öneriyor: Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension, yani "en yakın package.json dosyasına "type": "module" yaz ya da .mjs uzantısını kullan".

Bu dosyada ilk öneri işe yaramaz. "type" satırı yalnızca .js dosyalarını etkiler; .cjs uzantılı dosya package.json'da ne yazarsa yazsın CommonJS kalır. Klasöre "type": "module" yazan bir package.json koysan da aynı uyarıyı ve aynı hatayı alırsın; Node bu uyarıyı her durumda aynı kalıpla yazıyor.

Nasıl düzelir? Dosyanın adını .mjs yap ya da await'li kodu bir async fonksiyonun içine alıp o fonksiyonu çağır. Uyarıdaki "type": "module" önerisi, package.json'da "commonjs" yazan bir projedeki .js dosyası için doğrudur (aşağıdaki tablonun dördüncü satırı).

Ders 14'teki karar tablosuna yeni bir sütun ekleyelim: en üstte await nerede çalışır?

Dosyatype satırıNode nasıl çalıştırır?En üstte await
ders15.mjsfark etmezES modülüçalışır
ders15.cjsfark etmezCommonJSSyntaxError
ders15.js"module"ES modülüçalışır
ders15.js"commonjs"CommonJSSyntaxError
ders15.jsyoktahmin eder: en üstte await görünce ES modülüçalışır; package.json varsa uyarı da yazar

fetch: internetten gerçek veri

Asenkron kodun en çok kullanıldığı yer internetten veri almaktır. Riot Games oyunun verilerini (sürümler, şampiyonlar, eşyalar) Data Dragon adlı, herkese açık bir adreste yayımlıyor; kullanmak için hesap ya da anahtar gerekmiyor. Programların veri aldığı bu tür adreslere API denir; API'leri Ders 24'te ayrıntılı göreceksin.

Önce tarayıcıda bak

Şu adresi tarayıcının adres çubuğuna yapıştır: https://ddragon.leagueoflegends.com/api/versions.json

ddragon.leagueoflegends.com/api/versions.json
Tarayıcıda versions.json adresi açık: en üstte Pretty-print kutusu, altında tırnak içindeki sürüm numaralarından oluşan uzun bir liste: 16.20.1, 16.19.1, 16.18.1 ve devamı.

Gördüğün şey bir JSON dizisi: oyunun bütün sürümleri, en yenisi başta. Ders 12'de JSON.parse ile diziye çevirdiğin metinlerin aynısı. Chrome, Edge ya da Brave'de üstteki Pretty-print kutusunu işaretlersen her sürüm ayrı satırda görünür. Görüntü bu ders yazılırken alındı; sen baktığında listenin başında daha yeni sürümler olacak.

fetch ile aynı veriyi programdan almak

fetch (İngilizcede "gidip getir") Node'da hazır gelir, paket kurman gerekmez. fetch(adres) o adrese bir istek gönderir ve hemen bir Promise döndürür. Promise, sunucunun yanıtı gelince tamamlanır.

Yanıtın içindeki JSON'u almak için bir adım daha var: yanit.json(), o da bir Promise döndürür. Explorer'da ders15.mjs'e tıklayıp aç, içini sil ve şunu yaz:

ders15.mjs
const adres = "https://ddragon.leagueoflegends.com/api/versions.json"const yanit = await fetch(adres)console.log(yanit.status)console.log(yanit.ok) const surumler = await yanit.json()console.log(surumler.length + " sürüm var")console.log("En yeni sürüm: " + surumler[0])
Çıktıörnek, sende farklı çıkabilir
200true501 sürüm varEn yeni sürüm: 16.20.1

Sende sürüm sayısı ve en yeni sürüm farklı çıkacak; Riot her yamada listeye yeni bir sürüm ekliyor. İnternet bağlantın yoksa bu kod hata verir; o hatayı Sık hatalar bölümünde göreceksin. Satırlar:

  • yanit.status: durum kodu. Sunucu her yanıtın başına üç haneli bir kod koyar: 200 "tamam", 404 "bulunamadı", 403 "yasak", 500 "sunucuda hata" demek. Hepsini Ders 26'da göreceksin.
  • yanit.ok: durum kodu 200 ile 299 arasındaysa true. "İstek başarılı mı?" sorusunun kısa cevabı.
  • await yanit.json(): yanıtın gövdesini sonuna kadar okur ve JSON'dan JavaScript değerine çevirir; yani JSON.parse'ın işini de yapar.

Neden iki await? Yanıt iki parça gelir: önce başı (durum kodu ve başka bilgiler), sonra gövdesi (asıl veri). fetch'in Promise'i baş gelince tamamlanır. Gövde büyük olabilir ve parça parça gelir; onu sonuna kadar okumak ayrı bir bekleyiştir.

Senin programın Data Dragon sunucusu fetch(adres): "sürümleri ver" yanıtın başı: durum 200, ok: true ["16.20.1", "16.19.1", …] gövde: asıl veri, parça parça gelir 1 2 1 await fetch(adres): başı gelince biter, yanit objesi gelir 2 await yanit.json(): gövdenin sonunu bekler, diziye çevirir
Şekil 15.4 fetch'in iki bekleyişi: önce yanıtın başı, sonra gövdesi.

fetch 404'te hata fırlatmaz

Adreste küçük bir yanlış yap: versions.json yerine surumler.json yaz.

Sık hataSyntaxError: Unexpected token '<', "<?xml vers"... is not valid JSON

Ne zaman çıkar? Sunucu "böyle bir şey yok" dediğinde yanıtı denetlemeden json() ile okumaya kalkınca.

ders15.mjs
const adres = "https://ddragon.leagueoflegends.com/api/surumler.json"const yanit = await fetch(adres)const surumler = await yanit.json()console.log("En yeni sürüm: " + surumler[0])
Hata çıktısı
<anonymous_script>:1<?xml version="1.0" encoding="UTF-8"?>^ SyntaxError: Unexpected token '<', "<?xml vers"... is not valid JSON    at JSON.parse (<anonymous>)    ... Node.js v24.20.0

Neden? fetch, sunucu "böyle bir dosya yok" dese bile hata fırlatmaz. Ona göre istek başarılı oldu: soru gitti, cevap geldi; cevabın kötü haber olması onu ilgilendirmez. Hata bir sonraki adımda çıktı: yanit.json() gövdeyi JSON sanıp çevirmeye çalıştı, ama gövde <?xml ile başlayan bir hata sayfasıydı.

Mesajı Ders 12'den tanıyorsun: JSON.parse'a JSON olmayan bir metin verince çıkıyordu. Türü SyntaxError olsa da senin kodunda yazım hatası yok; JSON'a çevrilemeyen metin, sunucudan gelen o sayfa. <anonymous_script>:1 da senin dosyan değil, çevrilmeye çalışılan metnin 1. satırı.

Nasıl düzelir? Gövdeyi okumadan önce yanit.ok değerine bak; başarısızsa hatayı kendin fırlat (hemen aşağıda).

Bu adres için durum kodu ne? Data Dragon olmayan dosyalar için 404 (Not Found, bulunamadı) yerine 403 (Forbidden, yasak) döndürüyor; çoğu site 404 döndürür. Hangisi olursa olsun fetch hata fırlatmaz, yanit.ok ise false olur. Doğru kalıp şu: gövdeyi okumadan önce yanit.ok değerini denetle, başarısızsa hatayı kendin fırlat.

ders15.mjs
const adres = "https://ddragon.leagueoflegends.com/api/versions.json" async function surumleriGetir() {    const yanit = await fetch(adres)    if (!yanit.ok) {        throw new Error("İstek başarısız: " + yanit.status)    }    const surumler = await yanit.json()    return surumler} try {    const surumler = await surumleriGetir()    console.log("En yeni sürüm: " + surumler[0])} catch (hata) {    console.log("Sürümler alınamadı. " + hata.message)}
Çıktıörnek, sende farklı çıkabilir
En yeni sürüm: 16.20.1

Şimdi 1. satırdaki adresi yeniden surumler.json yap. Program bu kez çökmüyor, anlaşılır bir mesaj yazıyor:

ders15.mjs
const adres = "https://ddragon.leagueoflegends.com/api/surumler.json" async function surumleriGetir() {    const yanit = await fetch(adres)    if (!yanit.ok) {        throw new Error("İstek başarısız: " + yanit.status)    }    const surumler = await yanit.json()    return surumler} try {    const surumler = await surumleriGetir()    console.log("En yeni sürüm: " + surumler[0])} catch (hata) {    console.log("Sürümler alınamadı. " + hata.message)}
Çıktı
Sürümler alınamadı. İstek başarısız: 403

surumleriGetir içindeki throw, fonksiyonun döndürdüğü Promise'i reddetti; await bu reddi hata olarak fırlattı ve catch yakaladı. Hata tek bir yerde, okunaklı bir mesajla karşılandı.

Promise.all: aynı anda beklemek

Takımına üç davet göndereceksin. Her biri bir saniye sürüyor ve birbirlerini beklemeleri için hiçbir sebep yok. Aşağıdaki program önce üç daveti sırayla, sonra aynı anda bekliyor. Süreyi ölçmek için iki yeni araç var: console.time("ad") bir kronometre başlatır, console.timeEnd("ad") aynı adlı kronometreyi durdurup geçen süreyi yazar.

ders15.mjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function davetEt(oyuncu) {    await bekle(1000)    if (oyuncu === "Teemo") {        throw new Error(oyuncu + " daveti reddetti")    }    return oyuncu + " daveti kabul etti"} console.time("sırayla")await davetEt("Ahri")await davetEt("Lux")await davetEt("Garen")console.timeEnd("sırayla") console.time("aynı anda")const cevaplar = await Promise.all([davetEt("Ahri"), davetEt("Lux"), davetEt("Garen")])console.timeEnd("aynı anda")console.log(cevaplar)
Çıktıörnek, sende farklı çıkabilir
sırayla: 3.025saynı anda: 1.014s[  'Ahri daveti kabul etti',  'Lux daveti kabul etti',  'Garen daveti kabul etti']

Promise.all'a (all: hepsi) bir Promise dizisi verirsin, o da sana tek bir Promise döndürür. Bu Promise, dizideki Promise'lerin hepsi tamamlanınca tamamlanır; değeri, sonuçlardan oluşan bir dizidir. Üç davet aynı anda başladığı için toplam süre en uzun süren davet kadar oldu: bir saniye. Sırayla yazdığında ise her await bir öncekinin bitmesini bekledi ve süreler toplandı.

Sırayla üç ayrı await toplam ≈ 3 sn Ahri Lux Garen Aynı anda Promise.all toplam ≈ 1 sn Ahri Lux Garen hepsi 1. saniyede bitti 0 1 sn 2 sn 3 sn
Şekil 15.5 Sırayla beklerken süreler toplanır; Promise.all ile hepsi aynı anda başlar ve en uzunu kadar sürer.

Sonuç dizisinin sırası, işlerin bitiş sırası değil, senin verdiğin sıradır:

ders15.mjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function davetEt(oyuncu, ms) {    await bekle(ms)    console.log(oyuncu + " cevap verdi")    return oyuncu + " daveti kabul etti"} const cevaplar = await Promise.all([    davetEt("Ahri", 1000),    davetEt("Lux", 600),    davetEt("Garen", 200)])console.log(cevaplar)
Çıktı
Garen cevap verdiLux cevap verdiAhri cevap verdi[  'Ahri daveti kabul etti',  'Lux daveti kabul etti',  'Garen daveti kabul etti']

Garen ilk cevap verdi ama dizide yine üçüncü sırada. Böylece hangi sonucun hangi işe ait olduğunu hep bilirsin.

Biri reddedilirse Promise.all da hemen reddedilir, ötekilerin sonucunu beklemez; ret catch'e düşer. Takım dizisini Ders 11'deki map ile Promise dizisine çevirmek de çok yaygın bir kalıptır:

ders15.mjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function davetEt(oyuncu) {    await bekle(1000)    if (oyuncu === "Teemo") {        throw new Error(oyuncu + " daveti reddetti")    }    return oyuncu + " daveti kabul etti"} const takim = ["Ahri", "Teemo", "Garen"]try {    const cevaplar = await Promise.all(takim.map((oyuncu) => davetEt(oyuncu)))    console.log(cevaplar)} catch (hata) {    console.log("Takım kurulamadı: " + hata.message)}
Çıktı
Takım kurulamadı: Teemo daveti reddetti

takim.map((oyuncu) => davetEt(oyuncu)) her oyuncu için bir davet başlatıp Promise'lerden oluşan bir dizi üretiyor. Teemo'nun reddi bütün takımı düşürdü.

Sık hataforEach içindeki await beklenmiyor

Ne zaman çıkar? Bir dizinin her elemanı için asenkron bir iş yapıp hepsini beklemek isteyince forEach'e async bir fonksiyon verirsen. Ok fonksiyonunun önüne de async yazılabilir: async (oyuncu) => { … }. Hata mesajı yok, ama sıra yanlış:

ders15.mjs
function davetEt(oyuncu) {    return new Promise((resolve) => {        setTimeout(() => {            resolve(oyuncu + " daveti kabul etti")        }, 500)    })} const takim = ["Ahri", "Lux", "Garen"]takim.forEach(async (oyuncu) => {    const cevap = await davetEt(oyuncu)    console.log(cevap)})console.log("Herkes hazır, kuyruğa giriyoruz")
Çıktı
Herkes hazır, kuyruğa giriyoruzAhri daveti kabul ettiLux daveti kabul ettiGaren daveti kabul etti

forEach verdiğin fonksiyonu her eleman için çağırır ama dönen Promise'leri beklemez. Üç davet başlar, forEach hemen biter ve alttaki satır çalışır. Daha kimse kabul etmeden kuyruğa girdin.

Nasıl düzelir? Sırayla beklemek istiyorsan for...of içinde await kullan (Ders 5); döngü her turda gerçekten bekler:

ders15.mjs
function davetEt(oyuncu) {    return new Promise((resolve) => {        setTimeout(() => {            resolve(oyuncu + " daveti kabul etti")        }, 500)    })} const takim = ["Ahri", "Lux", "Garen"]for (const oyuncu of takim) {    const cevap = await davetEt(oyuncu)    console.log(cevap)}console.log("Herkes hazır, kuyruğa giriyoruz")
Çıktı
Ahri daveti kabul ettiLux daveti kabul ettiGaren daveti kabul ettiHerkes hazır, kuyruğa giriyoruz

Hepsini aynı anda beklemek istiyorsan yukarıdaki kalıbı kullan: const cevaplar = await Promise.all(takim.map((oyuncu) => davetEt(oyuncu))).

Sık hatalar

Bu derste karşılaşabileceğin öteki hatalar. Bütün derslerin hataları hata rehberinde.

Sık hataPromise { <pending> } ya da [object Promise] yazıyor: await unutmak

Ne zaman çıkar? async bir fonksiyonu çağırıp sonucunu await etmeden kullanınca. Hata mesajı yok; ama değer yerine Promise'in kendisini görürsün.

ders15.mjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function sampiyonGetir() {    await bekle(500)    return "Ahri"} const sampiyon = sampiyonGetir()console.log("Seçilen şampiyon: " + sampiyon)console.log(sampiyon)
Çıktı
Seçilen şampiyon: [object Promise]Promise { <pending> }

async fonksiyon her zaman bir Promise döndürür; sampiyon kutusunda "Ahri" değil, henüz bekleyen bir Promise var. Promise'i bir metne eklemeye çalışınca JavaScript [object Promise] yazar, console.log ise onu Promise { <pending> } diye gösterir.

Nasıl düzelir? Değeri istediğin yerde await yaz. Değişen tek satır 12. satır:

ders15.mjs
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function sampiyonGetir() {    await bekle(500)    return "Ahri"} const sampiyon = await sampiyonGetir()console.log("Seçilen şampiyon: " + sampiyon)console.log(sampiyon)
Çıktı
Seçilen şampiyon: AhriAhri

Bu satır bir async fonksiyonun içinde değilse dosyanın ES modülü (.mjs) olması gerekir. CommonJS bir dosyadaysan sampiyonGetir().then((sampiyon) => { … }) yazabilirsin.

Sık hataUnhandledPromiseRejection: yakalanmamış ret

Ne zaman çıkar? Bir Promise reddedildiğinde onu yakalayan bir catch (ya da try / catch) yoksa. Ret sebebi Error objesi değil de düz bir metinse Node bu uzun açıklamayı yazar:

ders15.js
const davet = new Promise((resolve, reject) => {    setTimeout(() => {        reject("Teemo daveti reddetti")    }, 500)}) davet.then((cevap) => {    console.log(cevap)})
Hata çıktısı
node:internal/process/promises:392      new UnhandledPromiseRejection(reason);      ^ UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Teemo daveti reddetti".    at throwUnhandledRejectionsMode (node:internal/process/promises:392:7)    ...    at process.processTicksAndRejections (node:internal/process/task_queues:106:37) {  code: 'ERR_UNHANDLED_REJECTION'} Node.js v24.20.0

Mesajın Türkçesi: "Bu hata ya catch bloğu olmayan bir async fonksiyonun içinde fırlatıldı ya da .catch() ile ilgilenilmeyen bir Promise reddedildi. Ret sebebi: Teemo daveti reddetti." Sebep bir Error objesi olsaydı Node, Ret yakalanmazsa bölümündeki gibi doğrudan o hatayı gösterirdi; sonuç yine aynı: program çöker.

Nasıl düzelir? Zincirin sonuna .catch((hata) => { … }) ekle ya da await'i try / catch içine al. Reddederken de düz metin yerine reject(new Error("…")) kullan; mesajın yanında hatanın nereden geldiğini gösteren yığın izi de gelir.

Sık hataTypeError: fetch failed

Ne zaman çıkar? İstek sunucuya hiç ulaşamayınca: adres yanlış yazılmışsa (alan adı yoksa) ya da internet bağlantın yoksa. fetch'in kendisinin reddedildiği durum budur.

ders15.mjs
const adres = "https://ddragon.leagueoflegends.cmo/api/versions.json"const yanit = await fetch(adres)console.log(yanit.status)
Hata çıktısı
node:internal/modules/run_main:107    triggerUncaughtException(    ^ [TypeError: fetch failed] {  [cause]: Error: getaddrinfo ENOTFOUND ddragon.leagueoflegends.cmo      at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:122:26) {    errno: -3008,    code: 'ENOTFOUND',    syscall: 'getaddrinfo',    hostname: 'ddragon.leagueoflegends.cmo'  }} Node.js v24.20.0

fetch failed tek başına pek bir şey söylemez; asıl sebep altındaki [cause] (sebep) satırında: getaddrinfo ENOTFOUND, yani "bu alan adının adresini bulamadım". Burada .com yerine .cmo yazılmış. İnternet bağlantın kopuksa doğru adreste de aynı hatayı alabilirsin.

Nasıl düzelir? Adresi harf harf denetle; tarayıcıda açılıyor mu bak; bağlantını kontrol et. Programın böyle bir durumda çökmemesi için fetch'i her zaman try / catch ile sar.

Sık hataTypeError: fetch(...).json is not a function

Ne zaman çıkar? İki await'i tek satırda birleştirmeye çalışınca.

ders15.mjs
const adres = "https://ddragon.leagueoflegends.com/api/versions.json"const surumler = await fetch(adres).json()console.log(surumler[0])
Hata çıktısı
file:///C:/kod/ogren/ders15.mjs:2const surumler = await fetch(adres).json()                                    ^ TypeError: fetch(...).json is not a function    at file:///C:/kod/ogren/ders15.mjs:2:37    ... Node.js v24.20.0

await, sağındaki ifadenin tamamı hesaplandıktan sonra bekler; yani önce fetch(adres).json() hesaplanmaya çalışılır. fetch(adres) bir Promise döndürür ve Promise'in json diye bir metodu yoktur; json() Promise'in değil, yanıtın metodudur. ES modülünde dosya yolunun file:/// ile başladığına da dikkat et.

Nasıl düzelir? İki adımı ayrı yaz: önce const yanit = await fetch(adres), sonra const surumler = await yanit.json().

Görevler

Görevleri ders15.js ya da ders15.mjs dosyasında yap; en üstte await kullanacaksan .mjs. Önce kendin dene, takılırsan ipuçlarını sırayla aç. Son iki görev internet bağlantısı istiyor.

Görev 1

Ejderha ve Vadi Alameti

Ejderha 1,5 saniye, Vadi Alameti yarım saniye sonra doğacak. Şu çıktıyı veren bir program yaz:

Metin
Zamanlayıcılar kurulduBu arada koridorda minyon kesiyorsunVadi Alameti doğduEjderha doğdu

Kural: kodda Ejderha'nın setTimeout'u, Vadi Alameti'ninkinden önce yazılsın. İkinci satırdaki mesaj iki zamanlayıcının altında yazılan bir console.log olsun.

İpucu 1

1,5 saniye kaç milisaniye? 1000 milisaniye bir saniye.

İpucu 2

Kuyruğa önce hangisi girer: önce kurulan zamanlayıcı mı, süresi önce dolan mı? Görev kuyruğunun hangi sırayla dolduğunu düşün.

İpucu 3

İskelet: console.log → setTimeout(…, 1500) → setTimeout(…, 500) → console.log.

Çözümü göster
ders15.js
console.log("Zamanlayıcılar kuruldu")setTimeout(() => {    console.log("Ejderha doğdu")}, 1500)setTimeout(() => {    console.log("Vadi Alameti doğdu")}, 500)console.log("Bu arada koridorda minyon kesiyorsun")
Çıktı
Zamanlayıcılar kurulduBu arada koridorda minyon kesiyorsunVadi Alameti doğduEjderha doğdu

Kuyruğa süresi önce dolan girer; kodda önce yazılmış olması fark etmez. Sınır denemesi: iki süreyi de 1000 yaparsan önce Ejderha doğdu gelir, çünkü süreler eşitken önce kurulan zamanlayıcı kuyruğa önce girer. Ejderha'nın süresini 0 yaparsan bile Bu arada… satırından sonra gelir: senkron kod bitmeden kuyruğa bakılmaz.

Görev 2

İksir: kendi Promise'in

iksirIc(hp) adında, Promise döndüren bir fonksiyon yaz. İksir içmek yarım saniye (500 ms) sürsün. Süre dolunca:

  • hp 0 ya da daha azsa Promise, mesajı "Ölü şampiyon iksir içemez" olan bir hatayla reddedilsin.
  • Değilse can 40 artsın ama 100'ü geçmesin; Promise yeni canla tamamlansın.

Fonksiyonu then / catch / finally ile kullan: başarıda Yeni can: …, hatada Olmadı: … yazsın, her durumda en sonda İksir şişesi boşaldı yazsın. iksirIc(30) çağrısının çıktısı:

Metin
Yeni can: 70İksir şişesi boşaldı

iksirIc(0) çağrısınınki:

Metin
Olmadı: Ölü şampiyon iksir içemezİksir şişesi boşaldı
İpucu 1

İskelet davetEt ile aynı: return new Promise((resolve, reject) => { setTimeout(() => { … }, 500) }).

İpucu 2

"100'ü geçmesin" için Ders 9'daki Math.min işini görür: Math.min(hp + 40, 100) iki sayıdan küçük olanı verir.

İpucu 3

setTimeout'un içinde bir if: hp <= 0 ise reject(new Error("Ölü şampiyon iksir içemez")), değilse resolve(Math.min(hp + 40, 100)).

Çözümü göster
ders15.js
function iksirIc(hp) {    return new Promise((resolve, reject) => {        setTimeout(() => {            if (hp <= 0) {                reject(new Error("Ölü şampiyon iksir içemez"))            } else {                resolve(Math.min(hp + 40, 100))            }        }, 500)    })} iksirIc(30)    .then((yeniHp) => {        console.log("Yeni can: " + yeniHp)    })    .catch((hata) => {        console.log("Olmadı: " + hata.message)    })    .finally(() => {        console.log("İksir şişesi boşaldı")    })
Çıktı
Yeni can: 70İksir şişesi boşaldı

Sınır denemesi: iksirIc(59) → 99, iksirIc(60) → 100, iksirIc(61) → yine 100 (sınır aşılmıyor), iksirIc(1) → 41. iksirIc(0) ve iksirIc(-5) reddedilir. hp < 0 yazsaydın 0 canlı ölü bir şampiyon iksir içebilirdi; sınırın tam üstünü denemek bu yüzden önemli.

Görev 3

TFT geri sayımı

TFT'de her tur bir hazırlık süresiyle başlar. geriSay(saniye) adında async bir fonksiyon yaz: verilen sayıdan 1'e kadar her saniye bir sayı yazsın, en sonda Savaş başladı! yazsın. Sayılar birer saniye arayla gelmeli; Savaş başladı! da 1'den bir saniye sonra. geriSay(3) çağrısının çıktısı:

Metin
321Savaş başladı!
İpucu 1

Bu derste yazdığın bekle fonksiyonunu kullan: await bekle(1000).

İpucu 2

Geriye doğru sayan bir for döngüsü: for (let kalan = saniye; kalan >= 1; kalan--). kalan--, kalan = kalan - 1'in kısa yazımı.

İpucu 3

Döngünün içinde önce sayıyı yaz, sonra bekle. forEach kullanma: await'i beklemediğini Promise.all bölümündeki hata kartında gördün.

Çözümü göster
ders15.js
function bekle(ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })} async function geriSay(saniye) {    for (let kalan = saniye; kalan >= 1; kalan--) {        console.log(kalan)        await bekle(1000)    }    console.log("Savaş başladı!")} geriSay(3)
Çıktı
321Savaş başladı!

Sınır denemesi: geriSay(0) döngüye hiç girmez ve hemen Savaş başladı! yazar. geriSay(1) önce 1 yazar, bir saniye sonra Savaş başladı!. Koşulu kalan >= 0 yazsaydın sayım 0'a kadar inerdi ve savaş bir saniye geç başlardı.

Görev 4

Şampiyonun unvanı

Data Dragon'da her şampiyonun ayrı bir dosyası var:

Metin
https://ddragon.leagueoflegends.com/cdn/SÜRÜM/data/tr_TR/champion/Ahri.json

SÜRÜM yerine en yeni sürümü (versions.json dizisinin ilk elemanı) yazmalısın; tr_TR Türkçe veri istediğini söyler. sampiyonUnvani(isim) adında async bir fonksiyon yaz: önce en yeni sürümü alsın, sonra şampiyonun dosyasını alıp unvanını döndürsün. Çağırdığın yerde şunu yazdır:

Metin
Ahri: Dokuz Kuyruklu Tilki

İki istekte de yanit.ok değerini denetle. Yanlış bir adla ("Ahrii") program çökmeden Hata: İstek başarısız: 403 yazsın.

İpucu 1

Önce adresi tarayıcıda aç ve JSON'a bak; SÜRÜM yerine versions.json'da gördüğün ilk sürümü yaz (ör. 16.20.1), yoksa sunucu 403 döndürür. Biçimi şöyle: { type, format, version, data: { Ahri: { id, key, name, title, … } } }. Unvan title alanında.

İpucu 2

İki istek de aynı işi yapıyor: getir, yanit.ok değerini denetle, JSON'a çevir. Bunu jsonGetir(adres) adında küçük bir async fonksiyona koy; sampiyonUnvani onu iki kez await etsin.

İpucu 3

Ad bir değişkende olduğu için köşeli parantezle oku (Ders 4): veri.data[isim].title. Adresi şablon metinle kur: `${ddragon}/cdn/${surum}/data/tr_TR/champion/${isim}.json`.

Çözümü göster
ders15.mjs
const ddragon = "https://ddragon.leagueoflegends.com" async function jsonGetir(adres) {    const yanit = await fetch(adres)    if (!yanit.ok) {        throw new Error("İstek başarısız: " + yanit.status)    }    const veri = await yanit.json()    return veri} async function sampiyonUnvani(isim) {    const surumler = await jsonGetir(ddragon + "/api/versions.json")    const surum = surumler[0]    const veri = await jsonGetir(`${ddragon}/cdn/${surum}/data/tr_TR/champion/${isim}.json`)    return veri.data[isim].title} try {    const unvan = await sampiyonUnvani("Ahri")    console.log("Ahri: " + unvan)} catch (hata) {    console.log("Hata: " + hata.message)}
Çıktı
Ahri: Dokuz Kuyruklu Tilki

İki await sırayla, çünkü ikinci adres birinci isteğin sonucuna (sürüme) muhtaç; burada Promise.all kullanamazsın. Sınır denemesi: "Ahrii" ile Hata: İstek başarısız: 403 çıkmalı. "ahri" (küçük harfle) de aynı hatayı verir: dosya adları büyük-küçük harfe duyarlı.

Görev 5

Bütün takım aynı anda

Bir takımın beş şampiyonunun unvanlarını getir: const takim = ["Garen", "Warwick", "Ahri", "Jinx", "Thresh"]. Önce beş dosyayı for...of ve await ile sırayla iste ve süreyi console.time ile ölç. Sonra aynı beş dosyayı aynı anda iste ve onun süresini de ölç.

En sonda unvanları verdiğin sırayla yazdır. Beklenen çıktı (süreler sende farklı olacak):

Metin
sırayla: 326.172msaynı anda: 195.205msGaren: Demacia'nın KudretiWarwick: Zaun'un Kafes Kaçkını BelasıAhri: Dokuz Kuyruklu TilkiJinx: DelifişekThresh: Zincirli Gardiyan

Adlardan biri yanlışsa program çökmesin, Hata: … yazsın.

İpucu 1

Bir önceki görevin jsonGetir fonksiyonunu al. En yeni sürümü bir kez, en başta öğren; beş dosyanın hepsi aynı sürümü kullanır.

İpucu 2

Bir şampiyonun satırını üreten bir async fonksiyon yaz: unvanSatiri(isim) dosyayı getirsin ve isim + ": " + unvan döndürsün. takim.map((isim) => unvanSatiri(isim)) sana beş Promise'lik bir dizi verir.

İpucu 3

const satirlar = await Promise.all(takim.map(…)) satırlardan oluşan bir dizi verir; sırası takim ile aynıdır. Sıralı ölçümde dönen satırı kullanmana gerek yok, yalnızca await unvanSatiri(isim) yaz.

Çözümü göster
ders15.mjs
const ddragon = "https://ddragon.leagueoflegends.com" async function jsonGetir(adres) {    const yanit = await fetch(adres)    if (!yanit.ok) {        throw new Error("İstek başarısız: " + yanit.status)    }    const veri = await yanit.json()    return veri} const surumler = await jsonGetir(ddragon + "/api/versions.json")const surum = surumler[0] async function unvanSatiri(isim) {    const veri = await jsonGetir(`${ddragon}/cdn/${surum}/data/tr_TR/champion/${isim}.json`)    return isim + ": " + veri.data[isim].title} const takim = ["Garen", "Warwick", "Ahri", "Jinx", "Thresh"]try {    console.time("sırayla")    for (const isim of takim) {        await unvanSatiri(isim)    }    console.timeEnd("sırayla")     console.time("aynı anda")    const satirlar = await Promise.all(takim.map((isim) => unvanSatiri(isim)))    console.timeEnd("aynı anda")     satirlar.forEach((satir) => console.log(satir))} catch (hata) {    console.log("Hata: " + hata.message)}
Çıktıörnek, sende farklı çıkabilir
sırayla: 326.172msaynı anda: 195.205msGaren: Demacia'nın KudretiWarwick: Zaun'un Kafes Kaçkını BelasıAhri: Dokuz Kuyruklu TilkiJinx: DelifişekThresh: Zincirli Gardiyan

Data Dragon dosyaları çok hızlı geldiği için fark iki kata yakın; istek ne kadar yavaşsa Promise.all o kadar kazandırır. Süreler her çalıştırmada biraz değişir, birkaç kez çalıştırıp karşılaştır. Sınır denemesi: "Thresh" yerine "Thressh" yazarsan sıralı döngü beşinci istekte hata fırlatır ve yalnızca Hata: İstek başarısız: 403 yazılır; Promise.all'a sıra bile gelmez. Takımı boş dizi ([]) yaparsan döngü hiç dönmez, Promise.all([]) da hemen boş bir diziyle tamamlanır: iki süre de neredeyse sıfır çıkar ve hiç satır yazılmaz.

Kendini sına

Soru 1

Harfler hangi sırayla yazılır?

JavaScript
console.log("A")setTimeout(() => {    console.log("B")}, 100)setTimeout(() => {    console.log("C")}, 0)console.log("D")
Cevabı göster

A, D, C, B. Önce senkron kodun tamamı çalışır: A ve D. Sonra kuyruğa süresi önce dolan girer: 0 milisaniyelik C, ardından 100 milisaniyelik B.

Soru 2

Bu program ne yazar? Değeri nasıl alırsın?

JavaScript
async function altinGetir() {    return 300} console.log(altinGetir())
Cevabı göster

Promise { 300 }. async fonksiyon her zaman bir Promise döndürür; return 300 o Promise'in değeri olur. Değeri almak için const altin = await altinGetir() yaz (bir async fonksiyonun içinde ya da .mjs dosyasının en üstünde) ya da altinGetir().then((altin) => console.log(altin)) kullan.

Soru 3

Bu program ne yazar?

JavaScript
const davet = new Promise((resolve, reject) => {    resolve("Kabul")    reject(new Error("Ret"))    resolve("Yine kabul")}) davet    .then((cevap) => console.log(cevap))    .catch((hata) => console.log(hata.message))
Cevabı göster

Yalnızca Kabul. Promise ilk resolve ile tamamlandı; sonuçlanan bir Promise bir daha değişmez, sonraki reject ve resolve çağrıları görmezden gelinir. Bu yüzden catch hiç çalışmaz.

Soru 4

İki parça ayrı ayrı yaklaşık kaç saniye sürer?

JavaScript
// 1. parçaawait bekle(1000)await bekle(2000)await bekle(3000) // 2. parçaawait Promise.all([bekle(1000), bekle(2000), bekle(3000)])
Cevabı göster

Birinci parça yaklaşık 6 saniye: her await bir öncekinin bitmesini bekler ve süreler toplanır (1 + 2 + 3). İkinci parça yaklaşık 3 saniye: üç bekleyiş aynı anda başlar, Promise.all en uzununun bitmesini bekler.

Soru 5

Sunucu doğru ama dosyanın adı yanlışsa bu fonksiyon ne yapar? Nasıl düzeltirsin?

JavaScript
async function surumleriGetir(adres) {    const yanit = await fetch(adres)    const surumler = await yanit.json()    return surumler}
Cevabı göster

fetch hata fırlatmaz; sunucu 403 ya da 404 döndürse de Promise tamamlanır. Ardından yanit.json() gövdedeki hata sayfasını JSON'a çevirmeye çalışır ve SyntaxError: Unexpected token '<' … is not valid JSON gibi yanıltıcı bir hata çıkar. Düzeltmek için gövdeyi okumadan önce if (!yanit.ok) { throw new Error("İstek başarısız: " + yanit.status) } ekle. Alan adı yanlış olsaydı durum farklı olurdu: sunucuya hiç ulaşılamaz ve fetch'in kendisi TypeError: fetch failed ile reddedilirdi.

Soru 6

Bu zincir ne yazar?

JavaScript
bekle(100)    .then(() => 2)    .then((sayi) => sayi * 10)    .then((sayi) => console.log(sayi))
Cevabı göster

20. then'e verilen fonksiyonun döndürdüğü değer, zincirdeki sonraki then'e gider: ilk halka 2 döndürür, ikincisi onu 10 ile çarpıp 20 döndürür, üçüncüsü yazar.

Soru 7

await'i nerelerde kullanabilirsin? Başka bir yerde kullanırsan ne olur?

Cevabı göster

İki yerde: başında async olan bir fonksiyonun içinde ve bir ES modülünün en üst düzeyinde (dosya .mjs ise ya da package.json'da "type": "module" yazıyorsa). Sıradan bir fonksiyonun içinde ya da bir CommonJS dosyasının en üstünde kullanırsan program hiç başlamaz: SyntaxError: await is only valid in async functions and the top level bodies of modules.

Kayıt noktası

ders15.cjs yalnızca hatayı görmek içindi; Explorer'da ona sağ tıklayıp Delete ile sil. Sonra dersin kodunu Git'e kaydet. VS Code terminalinde, C:\kod\ogren klasöründeyken:

Bir sonraki derste aynı yapıda çok sayıda obje üretmenin yolunu öğreneceksin: class, constructor ve this. Bu derste kalıp olarak yazdığın new sözcüğü de orada anlam kazanacak.

İşaret yalnızca bu tarayıcıda saklanır, kimseye gönderilmez.