7 puan yazan GN⁺ 2025-05-18 | 4 yorum | WhatsApp'ta paylaş
  • Explicit Resource Management önerisi, dosya tanıtıcıları, ağ bağlantıları gibi kaynakların yaşam döngüsünü açıkça kontrol etmenin yeni bir yolunu sunuyor
  • Bu özellik Chromium 134 ve V8 v13.8’den itibaren kullanılabiliyor
  • Dile eklenen parçalar
    • using ve await using bildirimleri ile Symbol.dispose, Symbol.asyncDispose sembollerinin eklenmesi sayesinde otomatik temizleme mekanizması sağlanıyor
    • DisposableStack, AsyncDisposableStack birden çok kaynağı güvenli şekilde gruplayıp serbest bırakıyor
    • SuppressedError, temizleme sırasında oluşan hata ile mevcut hatayı birlikte yönetiyor
  • Bu yöntem kod güvenliğini ve bakım yapılabilirliğini büyük ölçüde artırıyor, ayrıca kaynak sızıntılarını önlemede etkili oluyor
  • Mevcut try...finally desenini basitleştiriyor ve büyük ölçekli, karmaşık kaynak ortamlarında güvenilir kaynak işlemesini mümkün kılıyor

Açık Kaynak Yönetimi önerisine genel bakış

  • Explicit Resource Management önerisi, dosya tanıtıcıları, ağ bağlantıları gibi kaynakları açıkça oluşturup serbest bırakmaya yönelik yeni bir yöntem getiriyor
  • Başlıca bileşenler şunlar
    • using ve await using bildirimleri: scope sona erdiğinde kaynağı otomatik olarak serbest bırakır
    • [Symbol.dispose](), [Symbol.asyncDispose]() sembolleri: serbest bırakma (cleanup) davranışını uygulayan metotlar
    • global nesneler DisposableStack, AsyncDisposableStack: birden çok kaynağı gruplandırıp verimli şekilde yönetir
    • SuppressedError: kaynak temizliği sırasında oluşan hata ile mevcut hatayı birlikte içeren yeni hata türü
  • Bu özellikler, geliştiricilerin kaynakları ayrıntılı biçimde yönetmesine ve kodun performansı ile güvenliğini artırmasına odaklanıyor

using ve await using bildirimleri

  • using bildirimi senkron kaynaklar için, await using bildirimi ise asenkron kaynaklar için kullanılır
  • Bildirilen kaynak scope dışına çıktığında **Symbol.dispose veya **[Symbol.asyncDispose]() otomatik olarak çağrılır
  • Böylece senkron/asenkron kaynak sızıntısı sorunları azaltılır ve tutarlı serbest bırakma kodu yazılabilir
  • Bu anahtar kelimeler yalnızca kod bloklarında, for döngülerinde ve fonksiyon gövdelerinde kullanılabilir; en üst seviyede kullanılamaz
  • Örnek
    • Örneğin ReadableStreamDefaultReader kullanılırken, akışın yeniden kullanılabilmesi için reader.releaseLock() mutlaka çağrılmalıdır
    • Hata oluştuğunda bu çağrı atlanırsa, akışın kalıcı olarak kilitli kalması sorunu ortaya çıkar
  • Geleneksel yaklaşım
    • Geliştiriciler okuyucunun kilidinin açılmasını garanti etmek için try...finally bloğu kullanır
    • finally bloğuna reader.releaseLock() kodunun yazılması gerekir
  • İyileştirilmiş yaklaşım: using kullanımı
    • Serbest bırakma davranışını içeren disposable nesne (readerResource) oluşturulur
    • using readerResource = {...} deseni kullanıldığında, kod bloğundan çıkıldığı anda otomatik olarak serbest bırakılır
    • İleride web API’lerinde [Symbol.dispose] ve [Symbol.asyncDispose] desteği geldiğinde, ayrı bir sarmalayıcı nesne yazmadan otomatik yönetim mümkün olabilir

DisposableStack ve AsyncDisposableStack

  • DisposableStack ve AsyncDisposableStack, birden çok kaynağı verimli ve güvenli biçimde gruplamak için sunuluyor
  • Her stack’e kaynak eklenebilir; stack’in kendisi serbest bırakıldığında içindeki tüm kaynaklar ters sırayla serbest bırakılır
  • Bağımlılık ilişkileri olan karmaşık kaynak kümeleriyle çalışırken riski azaltır ve kodu sadeleştirir
  • Başlıca metotlar
    • use(value): stack’in en üstüne disposable bir kaynak ekler
    • adopt(value, onDispose): disposable olmayan bir kaynağı serbest bırakma callback’iyle birlikte ekler
    • defer(onDispose): kaynak olmadan yalnızca serbest bırakma davranışı ekler
    • move(): mevcut stack’teki tüm kaynakları yeni bir stack’e taşıyarak sahiplik devri sağlar
    • dispose(), asyncDispose(): stack içindeki tüm kaynakları serbest bırakır

Destek durumu ve kullanılabileceği zaman

  • Chromium 134, V8 v13.8 ve sonrasında açık kaynak yönetimi özelliği kullanılabiliyor
  • Gelecekte farklı web API’leriyle uyumluluğunun daha da genişlemesi bekleniyor

4 yorum

 
cichol 2025-05-18

await using data = await fn()
await ifadesinin sol tarafta da sağ tarafta da yer aldığı mucize

 
GN⁺ 2025-05-18
Hacker News görüşleri
  • Bu öneri, "function coloring" sorununa benzer bir his veriyor. Senkron ve asenkron fonksiyon ayrımı tüm özelliklere sızmayı sürdürüyor. Örneğin Symbol.dispose ile Symbol.asyncDispose, DisposableStack ile AsyncDisposableStack örneklerinde bu görülebiliyor. Java'nın sanal thread'lere (virtual threads) yönelmiş olmasından memnunum. JVM'e karmaşıklık ekleyerek uygulama geliştiricilerinin, kütüphane yazarlarının ve debugger'ların yükünü azaltan bir tercih olduğunu düşünüyorum

    • Asenkronluğu gizlemenin kod akışını anlamayı daha da zorlaştırdığı için buna katılmıyorum. Bir kaynağın asenkron olarak mı serbest bırakıldığını ve ağ sorunları gibi dış etkenlerden etkilenip etkilenemeyeceğini de bilmek isterim

    • Bugünlerde çoğu dilde "her şeyi asenkron yazmak normaldir" anlayışı gerçekten sinir bozucu. Purescript'i, kodu Eff (senkron etki) ya da Aff (asenkron etki) ile yazıp çağrı anında seçim yapılabilen tek örnek olarak görüyorum. Structured concurrency hoş, ama pratikte bu daha çok structured concurrency elde etmek için yapılan sözdizimsel bir işten ziyade, sunucuda birden çok üst seviye istek işleyicisine sahip olmak için yapılan bir çalışma gibi. Paralel işlemeyi kolaylaştıran bir araçtan ibaret

    • JVM'de bunun nasıl uygulandığını bilmiyorum ama genel olarak multithreading gerçekten sezgisel biçimde ele alınması zor bir teknoloji. Her türlü race condition, deadlock, livelock, starvation, memory visibility sorunu vb. üzerine yazılmış birçok kitap var. Bununla kıyaslandığında tek thread'li asenkron programlama çok daha az yük getiriyor. Function coloring sorununu kabullenmek, çok thread'li bir uygulamada "Heisenbug" debug etmekten daha az acı verici bir tercih

    • Java'nın bu tercihi yapmış olmasına gerçekten seviniyorum

    • Bunun nedeni, normal yürütme ile asenkron fonksiyonların birbirine kapalı Kartezyen kategoriler (closed Cartesian categories) oluşturması olabilir. Normal yürütme kategorisi asenkron kategoriye doğrudan gömülebilir. Her fonksiyonun bir kategorisi vardır (yani bir function color'ı vardır) ve bazı diller bunu daha açık biçimde ortaya koyar. Bu bir dil tasarımı tercihidir ve kategori teorisi thread'lerin ötesinde de güçlü biçimde kullanılabilir. Java ve thread tabanlı yaklaşım ise senkronizasyon sorunlarıyla karşı karşıya kalır; bunun özellikle zor yanı da budur. JavaScript ise monadik kategoriler içinde özellikle Continuation-passing tarzına kısıt koyar

  • defer fonksiyonuyla using kullanım örneğini görünce oldukça tazeleyici geldi. Birçok kişiye zaten sezgisel gelebilir ama yine de değinmeye değer olduğunu düşünüyorum

    • using önerisine dahil olan DisposableStack ve AsyncDisposableStack kullanılırsa callback kaydı yerleşik olarak destekleniyor. using blok kapsamlı olduğu için, kapsamlar arası ya da koşullu kayıt için buna ihtiyaç var. Ancak using değişkeni const gibi hemen başlatılmak zorunda olduğundan koşullu başlatma mümkün değil. Böyle durumlarda fonksiyonun en üstünde bir Stack oluşturup kullanılan kaynakları defer ile bu stack'e ekleme deseni gerekiyor. Gerekirse serbest bırakma zamanını fonksiyon seviyesine kolayca taşıyabiliyorsunuz

    • golang'a benziyor

  • Gerçekten iyi bir fikir olduğunu düşünüyorum ama,<p>web API stream'leri gibi yerlerde [Symbol.dispose] ile [Symbol.asyncDispose] birleşmesi gelecekte mümkün olsa bile, yakın vadede yalnızca bazı API'ler ve kütüphaneler bunu destekleyecek, geri kalanıysa (çoğu) desteklemeyecek. Sonunda ya using ile try/catch'i karıştırmak ya da en baştan her yerde try/catch kullanıp okunması daha kolay kodu seçmek gibi bir ikilem doğuyor. Bu da bu özelliğin "pratikte kullanılamaz" diye etiketlenmesi riskini yaratıyor. Aslında gerçek bir problemi çözen iyi bir tasarım olmasına rağmen benimsenmesinin zor olabilmesi üzücü

    • Bu tür desteği olmayan API'lerde DisposableStack kullanarak using uygulanabilir. Birden çok kaynağı birlikte ele alırken de try/catch'ten çok daha basit hale gelir. Runtime desteklediği anda, mevcut kaynakların güncellenmesini beklemeden hemen kullanılabilir

    • JavaScript'te bu durum 15 yıldır tekrar ediyor. Yeni dil özellikleri önce Babel gibi derleyicilere geliyor, sonra spesifikasyona giriyor, en sonunda da kararlı API'lere ve tarayıcılara ulaşması 3-4 yılı buluyor. Geliştiriciler zaten küçük wrapper'larla web API'lerini sarmaya alışkın ve çoğu zaman wrapper kullanmak polyfill'den daha iyi oluyor. Faydalı yeni bir dil özelliği geldiğinde "bunu kullanmak zor olur" diye hiç düşünmedim

    • Aslında birçok özellik zaten polyfill olarak uygulanmış durumda ve NodeJS ekosisteminin büyük bölümü bu deseni kullanıyor; kullanıcılar da yalnızca sözdizimi için transpiler kullanabiliyor. Geçen yıl bununla ilgili bir sunum hazırlarken NodeJS'te ve başlıca kütüphanelerde Symbol.dispose destekli API'nin epey fazla olduğunu fark ettim. Frontend'de yaşam döngüsü yönetim sistemleri olduğu için daha az kullanılabilir ama bazı durumlarda yine de yararlı. Test kütüphanelerinde ve backend'de yeterince yayılacağını düşünüyorum

    • TC39'un Rust'taki trait/protocol benzeri daha temel dil özelliklerine de odaklanması gerekiyor. Rust'ta yeni trait tanımlamak ve uygulamak nispeten kolayken, dinamik bir dil olup benzersiz symbol'lara sahip olan JS'te bunu eklemek çok daha kolay olabilir. Orphan rule gibi dezavantajlar var ama çok daha esnek bir yapıya evrilebilir

    • JavaScript dünyasında bu tür şeyler genelde polyfill ile çözülüyor

  • Bana C#'ı hatırlatıyor. IDisposable ve IAsyncDisposable sayesinde kilit yönetimi, kuyruklar, geçici scope yönetimi gibi soyutlamalarda çok faydalı

    • Öneriyi yazan kişinin Microsoft kökenli olması nedeniyle sözdizimi C#'a benzer şekilde belirlenmiş. İlgili GitHub issue'larında da aynı bağlam görülüyor

    • Tasarım temelde C#'tan alınmış. Aslında ilk öneri Python'un context manager'ına, Java'nın try-with-resources yapısına ve C#'ın using statement'ına da atıf yapıyor. using anahtar sözcüğü ve dispose hook metodu zaten oldukça açık ipuçları

  • JavaScript'te geriye dönük uyumluluğun önemli olduğunu anlıyorum ama [Symbol.dispose]() sözdizimi bana garip geliyor. Sanki bir dizide method handle varmış gibi karıştırıyorum. Bunun tam olarak ne olduğunu daha fazla öğrenmek istiyorum

    • Nesne literallerinde sol tarafta köşeli parantez içine alınmış dinamik anahtarların (dynamically computed property) ES6'dan beri neredeyse 10 yıldır kullanıldığı açıklanıyor. Ayrıca symbol'lar string ile referanslanamadığı için dinamik anahtar ile kısaltılmış method sözdizimi birleştiriliyor. Temelde bunun yeni bir sözdizimi olmadığı düşünülüyor

    • Sağlam kaynaklarla birlikte, bunun mevcut nesnelere symbol key atama biçiminden geldiği belirtiliyor. Doğal bir devam gibi

    • Başkaları bunun ne olduğunu zaten açıklamış ama neden böyle olduğuna dair açıklama eksik kalmış olabilir. Method adında Symbol kullanmak, mevcut method'larla çakışmadan bunun yeni bir API olduğunu garanti eder. Bir sınıfın yanlışlıkla disposable sayılmasını da önler

    • Dynamic property access kavramına değiniliyor. Nesne özelliklerine nokta (.) veya köşeli parantez ([]) ile erişilebildiği, string ve symbol'ların ikisinin de desteklendiği belirtiliyor. Symbol'lar benzersiz nesneler olarak karşılaştırılır ve "[Symbol.dispose]" gibi well known symbol'lar ile genişletilebilirlik sağlanır. Python'daki __dunder__ method'larına benzer bir fikir olduğu da anlatılıyor

    • Bu sözdizimi zaten yıllardır kullanılıyor. JavaScript iterator'ları da aynı şekilde çalışıyor ve bu neredeyse 10 yıl önce geldi

  • Kaynak yönetimi, özellikle lexical scope belirleyici olduğunda, JS'e structured concurrency getirmek için neden çaba harcandığı anlatılıyor. İlgili bir structured concurrency kütüphanesi de paylaşılıyor

  • Bun 1.0.23 ve sonrasında bu özellik zaten destekleniyor. Deneysel olarak denenebilir

  • Bu kadar karmaşık bir kod stilinde bir programın yürütme akışının nasıl anlaşılabildiğini ve kontrol edilebildiğini gerçekten bilmiyorum

    • Zaten meselenin özü bu. Web geliştirmenin %90'ı işe yaramayan ya da kimsenin istemediği yükseltmelerden oluşuyor; sonra ortaya çıkan sorunlar kalan %10'luk zamanda düzeltiliyor. Düşük bir ihtimalle bir gün birinin eskiden yazılmış koda bakması gerekiyor ve o noktada bug'ı yeni başlayan birine giriş görevi olarak bırakma fikri öneriliyor. Hatta 20 yıllık legacy sistemlerin hâlâ kullanıldığı bir gerçek

    • Örnek olarak verilen kodda ciddi sözdizimi hataları var ve gerçek JS'e pek benzemiyor. Ayrıca JS geliştiricileri while, promise chain ve finally gibi şeyleri böyle karışık kullanmaz; genelde await ya da uygun exception handling yapıları tercih edilir. İyi tasarlanmış kütüphanelerde de katman katman handler eklemek yerine DisposableStack ile çok daha kısa kod yazılabilir. Bugünlerde IIFE async fonksiyonlara bile çoğu zaman gerek kalmıyor

    • O dilde profesyonel olarak çalışıp anahtar sözcüklerin ne anlama geldiğine ve nasıl davrandığına alışınca kodu doğal biçimde anlayabiliyorsunuz. Haskell programcıları da benzer şekilde alışıyor

    • HN'de kod gömmek için her satırın başına en az 2 boşluk koymak gerekir. (Kodun anlaşılmasının zor olduğu konusuna katılıyorum)

    • Girintilemenin yardımcı olduğuna dair kısa bir öneri

  • Neden anonim (anonymous) sınıf destructor'ına gidilmediğini ya da Symbol dışı bir yapı kullanılmadığını merak ediyorum. İki ayrı Symbol'ün (senkron/asenkron) olması, soyutlamanın sızması anlamına gelmiyor mu?

    • Destructor'lar öngörülebilir bir davranış gerektirir (cleanup'ın açık olması gerekir), ancak gelişmiş GC'ler (garbage collector) bu desenle uyumlu değildir. Modern diller kapsam (scope) tabanlı cleanup destekler ve bunu HoF (higher-order function), özel hook'lar, callback kaydı gibi çeşitli yollarla uygular. Python başlangıçta destructor tabanlıydı (refcount GC) ama sınırlamaları yüzünden context manager getirildi

    • Diğer dillerde destructor'lar GC zamanlamasına bağlı çalıştığından güvenilmez olur. Buna karşılık dispose method'u değişkenin scope'u bittiğinde açıkça çağrılır; bu yüzden dosya kapatma veya kilit bırakma gibi işlemler için öngörülebilirdir. Symbol tabanlı method'lar mevcut özelliklerle çakışmayı önler ve genelde bunu kütüphane geliştiricisinin düşünmesi yeterlidir. Senkron/asenkron ayrımının açık olması gerekir ve await using a = await b() gibi biraz alışılmadık bir sözdizimi gerekebilir

    • GC dillerinde destructor'lar senkron çağrı için elverişli olmadığından çoğu zaman deterministik değildir. JS'te WeakRef ve FinalizationRegistry var ama Mozilla bile bunların öngörülemezliği nedeniyle kullanımını önermiyor

    • Bu yaklaşımın güçlü yanı, yalnızca sınıf örneklerinde değil başka hedeflerde de kullanılabilmesi

    • JavaScript'te anonymous property diye bir kavram olmadığı için soru başlı başına muğlak geliyor. Bu yöntem dışında gerçekçi bir alternatif olmadığı savunuluyor

  • Öneri metnindeki ilk örnek, bir kilidin try/finally ile güvenli biçimde bırakılmasını gösteriyor. Bu desen yalnızca uzun süre çalışan durumlarda mı önemli, yoksa tarayıcı ya da CLI ortamında hata nedeniyle süreç sonlansa bile kilit bırakılır mı diye merak ediyorum

    • Spesifikasyonda, blok yürütmesi normal şekilde tamamlansa da, exception/branch/exit ile sona erse de dispose'un mutlaka çalıştığı yazıyor. Yani using ile try/finally aynı davranıyor. Zorla sonlandırma (sürecin zorla kapatılması) spesifikasyon kapsamı dışında, ECMAScript buna karışmaz. Örnekteki stream JS içi bir nesne olduğu için interpreter ortadan kalkarsa kilit kavramının da anlamı kalmaz. Eğer söz konusu şey OS kaynağıysa (bellek, dosya vb.) genelde toplu cleanup işi OS tarafından yapılır ama davranış platforma göre değişir

    • Tarayıcıdaki web sayfası da bir bakıma çok uzun süre çalışan bir uygulamadır; hatta bazen sunucu süreçlerinden bile uzun ömürlüdür. Hata olduğunda sayfa ölmez ve exception dahil hatalar net kurallarla finally içinde ele alınır. NodeJS'te varsayılan olarak hata durumunda süreç kapanır ama sunucu senaryolarında farklı işlemler yaygındır. Yani serbest bırakma fonksiyonu finally içinde mutlaka çağrılır

 
ahwjdekf 2025-05-18

Bunca zaman kaynak gibi şeyleri zerre umursamadan gayet iyi yaşıyorduk. Sana birden ne oldu?