Ders 11 / 17
İptal ve Zaman Aşımı
İptal işareti kavramı, isteğin gerçekten durdurulması, zaman aşımının işaret üzerine kurulması, yarış tabanlı zaman aşımının neden yetersiz kaldığı ve işaretlerin birleştirilmesi.
İçindekiler
Önceki ders iki boşlukla kapandı: hiç yanıt vermeyen bir kaynak sonsuza kadar beklenir ve başlatılmış bir istekten vazgeçmenin yolu yoktur. İki boşluk da aynı eksikten doğar — eşzamansız bir işlemi dışarıdan etkileyecek bir kanal yoktur.
Bu ders o kanalı kurar. Kanalın adı iptal işareti (abort signal); zaman aşımı da bunun üzerine kurulan özel bir durumdur. Ölçüm akışı burada durdurulabilir hale gelir.
İptal İşareti
İptal düzeneği iki nesneden oluşur. Denetleyici (controller) iptali başlatan taraftadır
ve bir abort yöntemi taşır. İşaret (signal), işi yapan tarafa verilir; iptal edilip
edilmediğini bildirir ve iptal anında bir olay yayar.
Ayrım önemlidir: işareti alan taraf iptal edemez, yalnız iptali gözler. Böylece iptal yetkisi çağıranda kalır.
import { createServer } from "node:http"; const sunucu = createServer((istek, yanit) => { const kimlik = setTimeout(() => { yanit.writeHead(200, { "content-type": "application/json" }); yanit.end(JSON.stringify({ istasyon: "A1", deger: 21.4 })); }, 200); yanit.on("close", () => clearTimeout(kimlik)); }); await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar)); const taban = `http://127.0.0.1:${sunucu.address().port}`; const denetleyici = new AbortController(); setTimeout(() => denetleyici.abort(), 20); try { await fetch(`${taban}/olcum/A1`, { signal: denetleyici.signal }); console.log("bu satır çalışmaz"); } catch (hata) { console.log("istek durduruldu:", hata.name); } try { await fetch(`${taban}/olcum/A1`, { signal: AbortSignal.timeout(20) }); } catch (hata) { console.log("zaman aşımı:", hata.name); } sunucu.close();
istek durduruldu: AbortError zaman aşımı: TimeoutError
Sunucu iki yüz milisaniye sonra yanıt verecek biçimde kuruldu; iki istek de yirminci
milisaniyede sonlandı. Hata adları iki durumu ayırt eder: elle iptal AbortError, süre
sınırı TimeoutError üretir. Ayrım, kurtarma kararında kullanılır — kullanıcı vazgeçtiyse
yeniden denemek anlamsızdır, süre dolduysa anlamlı olabilir.
AbortSignal.timeout bir kısayoldur: belirtilen süre sonunda kendiliğinden iptal olan bir
işaret üretir. Zamanlayıcıyı elle kurmaya ve iptal etmeye gerek bırakmaz.
Sunucu tarafındaki close dinleyicisi de aynı düşüncenin karşılığıdır: istemci bağlantıyı
kestiğinde sunucu da bekleyen işini iptal eder. İptal, tek yönlü bir çağrı değil, iki uçta
da uygulanması gereken bir sözleşmedir.
Kendi İşlevinize İptal Eklemek
İptal, ana ortamın ayrıcalığı değildir. Söz döndüren her işlev, seçenek nesnesinde bir işaret kabul ederek aynı sözleşmeye katılabilir. Kurallar üçtür: işaret zaten iptal edilmişse hemen reddet, iptal olayında kaynakları serbest bırak ve reddet, dinleyiciyi bir kez çalışacak biçimde kaydet.
const KAYNAK = { A1: { gecikme: 30, deger: 21.4 }, B2: { gecikme: 10, deger: 19.8 }, }; function olcumGetir(istasyon, secenekler = {}) { const isaret = secenekler.signal; return new Promise((karar, red) => { if (isaret?.aborted) { red(isaret.reason); return; } const kayit = KAYNAK[istasyon]; const kimlik = setTimeout(() => { console.log("kaynak: iş tamamlandı —", istasyon); karar({ istasyon, deger: kayit.deger }); }, kayit.gecikme); isaret?.addEventListener( "abort", () => { clearTimeout(kimlik); console.log("kaynak: iş durduruldu —", istasyon); red(isaret.reason); }, { once: true }, ); }); } const denetleyici = new AbortController(); setTimeout(() => denetleyici.abort(new Error("toplama iptal edildi")), 15); try { await olcumGetir("A1", { signal: denetleyici.signal }); } catch (hata) { console.log("çağıran gördü:", hata.message); } await new Promise((karar) => setTimeout(karar, 40)); console.log("bekleme bitti; A1 için başka çıktı olmadı");
kaynak: iş durduruldu — A1 çağıran gördü: toplama iptal edildi bekleme bitti; A1 için başka çıktı olmadı
Son satır, iptalin gerçekten iş durdurduğunun kanıtıdır: kırk milisaniye daha beklendiği halde “iş tamamlandı” iletisi hiç yazılmadı, çünkü zamanlayıcı temizlendi.
abort yöntemine verilen değer, işaretin reason alanına yerleşir ve reddetme nedeni
olur. Verilmezse ana ortam AbortError adlı bir hata üretir. Kendi hatanızı vermek,
iptal nedenini çağrı yığıtı olmadan taşımanın en doğrudan yoludur.
Yarış Tabanlı Zaman Aşımı Neden Yetersiz
Zaman aşımı, birleştiriciler bilindiğinde ilk akla gelen biçimde de yazılabilir: işi bir zamanlayıcıyla yarıştırmak. Bu çözüm beklemeyi keser, ama işi kesmez.
const KAYNAK = { A1: { gecikme: 30, deger: 21.4 }, }; function olcumGetir(istasyon) { return new Promise((karar) => { const kayit = KAYNAK[istasyon]; setTimeout(() => { console.log("kaynak: iş yine de tamamlandı —", istasyon); karar({ istasyon, deger: kayit.deger }); }, kayit.gecikme); }); } function zamanAsimi(sure) { return new Promise((karar, red) => { setTimeout(() => red(new Error(`zaman aşımı: ${sure} ms`)), sure); }); } try { await Promise.race([olcumGetir("A1"), zamanAsimi(10)]); } catch (hata) { console.log("çağıran gördü:", hata.message); } await new Promise((karar) => setTimeout(karar, 40)); console.log("bekleme bitti");
çağıran gördü: zaman aşımı: 10 ms kaynak: iş yine de tamamlandı — A1 bekleme bitti
Çağıran onuncu milisaniyede hata aldı; iş otuzuncu milisaniyede tamamlandı. Yani sunucuya gönderilen istek sürdü, yanıt geldi, bellek ve bağlantı tutuldu — yalnız sonucu kimse kullanmadı.
Sonuçları üç başlıkta toplanır: kaynak boşa harcanır, sonuçlanmamış söz ve kapattığı değerler bellekte kalır, ve yan etkili bir işlemde — örneğin bir kaydın yazılmasında — “zaman aşımına uğradı” denen işlem aslında tamamlanmış olabilir.
Kural şudur: yarış beklemeyi sonlandırır, iptal işi sonlandırır. İkisi birbirinin yerine geçmez; doğru zaman aşımı, süre dolduğunda iptali tetikleyendir.
İşaretleri Birleştirmek
Bir işlemin birden çok iptal nedeni olabilir: kullanıcı vazgeçti, süre doldu, üst düzey
işlem iptal edildi. AbortSignal.any, birden çok işareti tek işarette toplar; hangisi
önce iptal olursa birleşik işaret onun nedeniyle iptal olur.
const kullanici = new AbortController(); const birlesik = AbortSignal.any([kullanici.signal, AbortSignal.timeout(30)]); birlesik.addEventListener("abort", () => { console.log("birleşik işaret durduruldu; neden:", birlesik.reason.name); }); setTimeout(() => kullanici.abort(), 10); await new Promise((karar) => setTimeout(karar, 50)); console.log("kullanıcı işareti durduruldu mu:", kullanici.signal.aborted);
birleşik işaret durduruldu; neden: AbortError kullanıcı işareti durduruldu mu: true
Kullanıcı iptali onuncu milisaniyede geldiği için birleşik işaret onunla sonuçlandı; otuz milisaniyelik süre sınırı hiç devreye girmedi. Bu kalıp, iptali çağrı ağacı boyunca taşımayı mümkün kılar: üst düzey bir işlemin işareti, alt işlemlerin kendi süre sınırlarıyla birleştirilerek aşağı aktarılır.
İptalin Sınırları
İki sınır açıkça bilinmelidir.
İptal işbirliğine dayanır. İşareti gözlemeyen bir işlev iptal edilemez; iptal isteği yalnız işareti dinleyenlere ulaşır. Bu yüzden söz döndüren her işlev, uzun sürebilecek işlerde işaret parametresini kabul etmelidir.
İptal geri alma değildir. Sunucuya ulaşmış bir isteğin sunucudaki etkisi iptalle geri alınmaz; yalnız yanıtın beklenmesinden vazgeçilir. Yan etkili işlemlerde bu ayrım, sonraki dersteki yeniden deneme kararının temelini oluşturur.
Özet
- İptal, denetleyici ve işaret olmak üzere iki nesneyle kurulur; işareti alan taraf iptali yalnız gözler.
- Söz döndüren kendi işlevleriniz, işareti kabul edip iptal olayında kaynaklarını serbest bırakarak aynı sözleşmeye katılabilir.
AbortSignal.timeoutsüre sınırını işaret olarak üretir; elle iptalAbortError, süre sınırıTimeoutErrorverir.- Yarış tabanlı zaman aşımı yalnız beklemeyi keser; iş sürer, kaynak tutulur, yan etki gerçekleşebilir.
AbortSignal.anybirden çok iptal nedenini tek işarette toplar ve iptalin çağrı ağacı boyunca aktarılmasını sağlar.
Sonraki Adım
İptal ve zaman aşımı, başarısızlığı belirlenmiş hale getirdi: artık bir isteğin ne zaman biteceği biliniyor. Geriye başarısızlıktan sonra ne yapılacağı kalıyor. Sonraki ders geçici hataları kalıcı olanlardan ayırır, hangi işlemlerin güvenle yeniden denenebileceğini belirler, bekleme sürelerinin neden artırılarak seçildiğini ve deneme bütçesinin neden sınırlı tutulduğunu gösterir. Ölçüm akışı böylece hem durdurulabilir hem de dayanıklı hale gelecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.