İçeriğe geç
academia.sh

Ders 04 / 17

Geri Çağrılar

Hata öncelikli geri çağrı sözleşmesi, sıralı ve birlikte işlemenin elle kurulması, iç içe geçme sorunu ve denetimin çağrılan tarafa devredilmesinin doğurduğu belirsizlikler.

İçindekiler

Zamanlayıcılar geri çağrıyı zamana bağladı. Gerçek bir kaynakta bağlanılan şey zaman değil sonuçtur: ölçüm hazır olduğunda haber ver, olmazsa nedenini bildir. Bu, eşzamansız programlamanın ilk yaygın arayüzüdür ve sözleri anlamak için önce onun sınırlarını görmek gerekir.

Bu ders ölçüm kaynağını geri çağrı tabanlı bir arayüze dönüştürür, üç istasyonu önce sırayla sonra birlikte okur ve bu yaklaşımın nerede yetersiz kaldığını gösterir.

Hata Öncelikli Sözleşme

Eşzamansız bir işlev sonucunu return ile veremez; çünkü sonuç, işlev döndüğünde henüz yoktur. Bunun yerine sonucu bir geri çağrı (callback) aracılığıyla verir.

Hata da aynı yoldan gelmelidir: önceki derste görüldüğü gibi, geri çağrının içinden atılan bir hata çağıranın try bloğuna ulaşmaz. Yerleşik çözüm, geri çağrının ilk parametresini hataya ayırmaktır — hata öncelikli geri çağrı (error-first callback) sözleşmesi.

const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  if (kayit === undefined) {
    setTimeout(() => gericagri(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
    return;
  }
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

olcumGetir("A1", (hata, olcum) => {
  if (hata) {
    console.log("A1 hatası:", hata.message);
    return;
  }
  console.log("A1 ölçümü:", olcum.deger);
});

olcumGetir("Z9", (hata, olcum) => {
  if (hata) {
    console.log("Z9 hatası:", hata.message);
    return;
  }
  console.log("Z9 ölçümü:", olcum.deger);
});

console.log("istekler kaydedildi");
istekler kaydedildi
Z9 hatası: bilinmeyen istasyon: Z9
A1 ölçümü: 21.4

Üç ayrıntı sözleşmenin parçasıdır. Birincisi, hata yolunda bile geri çağrı eşzamansız çağrılır; bilinmeyen istasyon hatası bile setTimeout ile bir sonraki tura bırakıldı. İkincisi, hata varken ikinci parametre anlamsızdır. Üçüncüsü, hata denetimi her çağrı noktasında elle yazılır — bu, dersin sonunda tekrar edilecek bir maliyettir.

Sıralı İşleme ve İç İçe Geçme

Üç istasyonu sırayla okuyup ortalamayı hesaplamak isteyin. Her adımın sonucu bir sonraki adımın geri çağrısında kullanılabildiği için, adımlar iç içe girer.

const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  if (kayit === undefined) {
    setTimeout(() => gericagri(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
    return;
  }
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

olcumGetir("A1", (hataA, a) => {
  if (hataA) {
    console.log("hata:", hataA.message);
    return;
  }
  olcumGetir("B2", (hataB, b) => {
    if (hataB) {
      console.log("hata:", hataB.message);
      return;
    }
    olcumGetir("C3", (hataC, c) => {
      if (hataC) {
        console.log("hata:", hataC.message);
        return;
      }
      const ortalama = (a.deger + b.deger + c.deger) / 3;
      console.log("sıra:", [a.istasyon, b.istasyon, c.istasyon].join(" -> "));
      console.log("ortalama:", ortalama.toFixed(2));
    });
  });
});
sıra: A1 -> B2 -> C3
ortalama: 21.43

Program doğrudur, ama üç gözlem yapılabilir. Girinti, mantıksal karmaşıklıkla değil adım sayısıyla artar; on adım on kat girinti demektir. Hata denetimi üç kez, birbirinin aynısı biçimde tekrarlanır. Son olarak, akışın sonucunu kullanacak kod en içeride kalır; adım eklemek en dıştan en içe kadar dokunmayı gerektirir.

Bu düzenin yaygın adı geri çağrı piramididir. Asıl sorun görüntü değil, birleştirilebilir olmamasıdır: iki eşzamansız adımı birleştiren genel bir işleç yoktur; her birleşim elle yazılır.

Birlikte İşleme: Eşgüdümün Bedeli

Üç istasyon birbirinden bağımsızsa sırayla beklemek gereksizdir; üçü birlikte başlatılabilir. Bu durumda sonuçların toplanması, sayaç ve bayrak tutan bir eşgüdüm işlevi gerektirir.

const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  if (kayit === undefined) {
    setTimeout(() => gericagri(new Error(`bilinmeyen istasyon: ${istasyon}`)), 0);
    return;
  }
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

function tumunuGetir(istasyonlar, gericagri) {
  const sonuclar = new Array(istasyonlar.length);
  let kalan = istasyonlar.length;
  let bitti = false;

  istasyonlar.forEach((istasyon, dizin) => {
    olcumGetir(istasyon, (hata, olcum) => {
      if (bitti) return;
      if (hata) {
        bitti = true;
        gericagri(hata);
        return;
      }
      console.log("geldi:", istasyon);
      sonuclar[dizin] = olcum;
      kalan -= 1;
      if (kalan === 0) {
        bitti = true;
        gericagri(null, sonuclar);
      }
    });
  });
}

tumunuGetir(["A1", "B2", "C3"], (hata, olcumler) => {
  if (hata) {
    console.log("hata:", hata.message);
    return;
  }
  console.log("sonuç sırası:", olcumler.map((o) => o.istasyon).join(" "));
});
geldi: B2
geldi: C3
geldi: A1
sonuç sırası: A1 B2 C3

Geliş sırası gecikmelere göre B2, C3, A1 oldu; buna karşın sonuç dizisi istek sırasını korudu. Bunu sağlayan şey, sonuçların dizine göre yerleştirilmesidir — sıraya güvenmek yerine konum kullanmak, eşzamansız toplamada temel kalıptır.

bitti bayrağı da gereklidir: hata sonrası gelen sonuçların geri çağrıyı ikinci kez tetiklemesini engeller. Yani doğru bir “hepsini bekle” işlevi, sayaç ve bayrak olmak üzere iki ayrı durum tutar. Bu iş, sözlerle birlikte kütüphaneye taşınacak ve tek satıra inecek.

Denetimin Çağrılana Devri

Geri çağrı verdiğinizde, işlevinizin ne zaman ve kaç kez çalışacağına siz karar vermezsiniz; çağırdığınız kod karar verir. Buna denetimin devri (inversion of control) denir ve üç somut belirsizlik doğurur.

Geri çağrı hiç çağrılmayabilir. Kaynak yanıt vermezse hiçbir şey olmaz; program ne sonuç ne hata görür, sessizce bekler. Zaman aşımı, bu boşluğu kapatmak için ayrıca kurulur.

Geri çağrı birden çok kez çağrılabilir. Sözleşme bunu yasaklar ama dil zorlamaz.

function hataliOlcumGetir(istasyon, gericagri) {
  setTimeout(() => {
    gericagri(null, { istasyon, deger: 21.4 });
    gericagri(null, { istasyon, deger: 21.4 });
  }, 5);
}

let adet = 0;
let toplam = 0;

hataliOlcumGetir("A1", (hata, olcum) => {
  adet += 1;
  toplam += olcum.deger;
  console.log("adet:", adet, "toplam:", toplam.toFixed(1));
});
adet: 1 toplam: 21.4
adet: 2 toplam: 42.8

Tek bir ölçüm iki kez toplandı. Hata, toplayan tarafta değil kaynaktadır; ama sonucuna toplayan taraf katlanır.

Geri çağrı bazen eşzamanlı çağrılabilir. Önbellek isabetinde beklemeye gerek olmadığı düşünülerek geri çağrının doğrudan çağrılması, akışın sırasını değiştirir.

const KAYNAK = { B2: { gecikme: 10, deger: 19.8 } };

function olcumGetir(istasyon, gericagri) {
  const kayit = KAYNAK[istasyon];
  setTimeout(() => gericagri(null, { istasyon, deger: kayit.deger }), kayit.gecikme);
}

const onbellek = new Map();

function onbellekliGetir(istasyon, gericagri) {
  if (onbellek.has(istasyon)) {
    gericagri(null, onbellek.get(istasyon));
    return;
  }
  olcumGetir(istasyon, (hata, olcum) => {
    onbellek.set(istasyon, olcum);
    gericagri(hata, olcum);
  });
}

let durum = "hazırlanıyor";

onbellekliGetir("B2", (hata, olcum) => {
  console.log("1 — ilk çağrı, durum:", durum);
  durum = "işleniyor";
  onbellekliGetir("B2", () => {
    console.log("2 — önbellekten gelen çağrı, durum:", durum);
  });
  durum = "iç çağrıdan sonra";
  console.log("3 — iç çağrıdan sonraki satır, durum:", durum);
});

durum = "hazır";
1 — ilk çağrı, durum: hazır
2 — önbellekten gelen çağrı, durum: işleniyor
3 — iç çağrıdan sonraki satır, durum: iç çağrıdan sonra

İkinci çağrının geri çağrısı, kendisinden sonraki satırdan önce çalıştı; çünkü önbellek isabetinde eşzamanlı çağrıldı. Aynı arayüz, aynı çağrı, iki farklı sıra. Kural nettir: bir arayüz geri çağrıyı bazen eşzamanlı bazen eşzamansız çağırmamalıdır. Önbellek isabetinde de sonuç bir sonraki tura bırakılmalıdır.

Geri Çağrının Bıraktığı Boşluk

Bu dersteki programlar çalışır ve hiçbirinde dil hatası yoktur. Buna karşın hepsi aynı eksikleri paylaşır: hata yolu her düzeyde elle yazılır, try/catch işe yaramaz, adımların birleştirilmesi için genel bir işleç yoktur, ve sözleşme ihlalleri (hiç çağırmama, iki kez çağırma, eşzamanlı çağırma) dil tarafından engellenmez.

Sonuç, eşzamansız bir işlemin birinci sınıf bir değer olmamasıdır. Geri çağrı, sonucu alan tarafı bildirir; ama işlemin kendisini temsil eden, saklanabilen, döndürülebilen, başkasına verilebilen bir nesne yoktur.

Özet

  • Eşzamansız bir işlev sonucunu döndüremez; hata öncelikli geri çağrı sözleşmesi hem sonucu hem hatayı tek bir yoldan iletir.
  • Sıralı adımlar geri çağrılarla iç içe girer; girinti ve hata denetimi adım sayısıyla büyür.
  • Birlikte çalışan istekleri toplamak sayaç ve bayrak gerektirir; sonuçlar geliş sırasına değil konuma göre yerleştirilir.
  • Denetimin devri üç belirsizlik doğurur: geri çağrının hiç çağrılmaması, birden çok kez çağrılması ve bazen eşzamanlı çağrılması.
  • Geri çağrı düzeninde eşzamansız işlem birinci sınıf bir değer değildir; bu nedenle birleştirilemez.

Sonraki Adım

Bu boşluğun yanıtı, “sonucu henüz belli olmayan bir işlem”i bir değer olarak temsil etmektir. Sonraki ders bu değeri tanımlar: söz. Sözün üç durumu, durumların yalnız bir kez değişebilmesi, geri çağrıların mikro göreve alınması ve zincirlemenin her adımda yeni bir söz üretmesi — bu dersteki dört eksiğin dördü de bu kurallardan çıkacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat