7 poin oleh GN⁺ 2025-05-18 | 4 komentar | Bagikan ke WhatsApp
  • Proposal Explicit Resource Management adalah cara baru untuk mengontrol siklus hidup sumber daya seperti file handle dan koneksi jaringan secara jelas
  • Fitur ini tersedia mulai Chromium 134 dan V8 v13.8
  • Bagian yang ditambahkan ke bahasa
    • Memperkenalkan deklarasi using dan await using serta simbol Symbol.dispose, Symbol.asyncDispose untuk menyediakan mekanisme pembersihan otomatis
    • DisposableStack, AsyncDisposableStack memungkinkan pengelompokan dan pelepasan beberapa sumber daya secara aman
    • SuppressedError mengelola error yang terjadi saat pembersihan bersama error yang sudah ada
  • Pendekatan ini sangat meningkatkan keamanan kode dan kemudahan pemeliharaan, serta efektif mencegah kebocoran sumber daya
  • Menyederhanakan pola try...finally yang ada dan memungkinkan penanganan sumber daya yang andal di lingkungan sumber daya kompleks berskala besar

Ikhtisar proposal manajemen sumber daya eksplisit

  • Proposal Explicit Resource Management memperkenalkan cara baru untuk membuat dan melepaskan sumber daya seperti file handle dan koneksi jaringan secara jelas
  • Komponen utamanya adalah sebagai berikut
    • Deklarasi using dan await using: otomatis melepaskan sumber daya saat scope berakhir
    • Simbol [Symbol.dispose]() dan [Symbol.asyncDispose]() : metode untuk mengimplementasikan tindakan pelepasan (cleanup)
    • Objek global DisposableStack, AsyncDisposableStack: mengelompokkan beberapa sumber daya agar dapat dikelola dengan efisien
    • SuppressedError: tipe error baru yang mencakup error saat pembersihan sumber daya dan error yang sudah ada
  • Fitur-fitur ini berfokus pada membantu developer mengelola sumber daya secara rinci dan meningkatkan performa serta keamanan kode

Deklarasi using dan await using

  • Deklarasi using digunakan untuk sumber daya sinkron, sedangkan deklarasi await using digunakan untuk sumber daya asinkron
  • Sumber daya yang dideklarasikan akan **secara otomatis memanggil Symbol.dispose atau **Symbol.asyncDispose` saat keluar dari scope
  • Dengan ini, masalah kebocoran sumber daya sinkron/asinkron dapat dikurangi dan kode pelepasan yang konsisten dapat ditulis
  • Keyword ini hanya dapat digunakan di dalam blok kode, loop for, atau body fungsi, dan tidak dapat digunakan di level teratas
  • Contoh
    • Misalnya, saat menggunakan ReadableStreamDefaultReader, reader.releaseLock() harus dipanggil agar stream dapat digunakan kembali
    • Jika terjadi error dan pemanggilan ini terlewat, stream bisa terkunci secara permanen
  • Cara tradisional
    • Developer menggunakan blok try...finally untuk menjamin pelepasan lock pembaca
    • Perlu menulis kode reader.releaseLock() di blok finally
  • Cara yang ditingkatkan: pengenalan using
    • Membuat objek disposable (readerResource) yang menyertakan tindakan pelepasan
    • Jika menggunakan pola using readerResource = {...}, sumber daya akan otomatis dilepaskan segera setelah keluar dari blok kode
    • Jika nanti web API mendukung [Symbol.dispose] dan [Symbol.asyncDispose], pengelolaan otomatis mungkin bisa dilakukan tanpa menulis objek pembungkus terpisah

DisposableStack dan AsyncDisposableStack

  • DisposableStack dan AsyncDisposableStack diperkenalkan untuk mengelompokkan beberapa sumber daya secara efisien dan aman
  • Tambahkan sumber daya ke setiap stack, lalu saat stack itu sendiri dilepaskan, semua sumber daya di dalamnya dilepaskan dalam urutan terbalik
  • Ini mengurangi risiko dan menyederhanakan kode saat menangani kumpulan sumber daya kompleks yang memiliki hubungan ketergantungan
  • Metode utama
    • use(value): menambahkan sumber daya disposable ke bagian atas stack
    • adopt(value, onDispose): menambahkan sumber daya non-disposable dengan callback pelepasan yang terikat
    • defer(onDispose): hanya menambahkan tindakan pelepasan tanpa sumber daya
    • move(): memindahkan semua sumber daya dari stack saat ini ke stack baru sehingga kepemilikan bisa dipindahkan
    • dispose(), asyncDispose(): melepaskan seluruh sumber daya dalam stack

Status dukungan dan kapan bisa digunakan

  • Fitur manajemen sumber daya eksplisit tersedia di Chromium 134, V8 v13.8 dan versi lebih baru
  • Ke depannya, diharapkan kompatibilitasnya akan meluas ke berbagai web API

4 komentar

 
cichol 2025-05-18

await using data = await fn()
Keajaiban await yang muncul di sisi kiri dan kanan

 
tested 2025-05-18

Kekuatan super baru JavaScript: manajemen sumber daya eksplisit

https://typescriptlang.org/docs/handbook/…

 
GN⁺ 2025-05-18
Komentar Hacker News
  • Usulan ini terasa mirip dengan masalah "warna fungsi". Pemisahan antara fungsi sinkron dan asinkron terus merembes ke semua fitur. Misalnya bisa dilihat pada kasus Symbol.dispose dan Symbol.asyncDispose, serta DisposableStack dan AsyncDisposableStack. Saya puas Java memilih menuju virtual threads. Menurut saya itu pilihan yang mengurangi beban pengembang aplikasi, penulis library, dan debugger dengan menambahkan kompleksitas ke JVM

    • Saya tidak setuju karena menyembunyikan asinkronitas justru membuat alur kode lebih sulit dipahami. Saya juga ingin tahu apakah resource dibebaskan secara asinkron, dan apakah itu bisa terpengaruh faktor eksternal seperti masalah jaringan

    • Saya benar-benar kesal dengan fenomena di banyak bahasa sekarang bahwa "semua kode harus ditulis asinkron" dianggap sebagai hal yang lumrah. Saya melihat Purescript sebagai satu-satunya contoh yang menulis kode dengan Eff (efek sinkron) atau Aff (efek asinkron) lalu bisa memilih saat pemanggilan. Structured concurrency memang keren, tetapi pada praktiknya lebih dekat ke pekerjaan untuk memiliki beberapa top-level request handler di server daripada pekerjaan sintaksis untuk memperoleh structured concurrency. Pada akhirnya itu cuma sarana untuk mempermudah pemrosesan paralel

    • Saya tidak tahu bagaimana ini diimplementasikan di JVM, tetapi secara umum multithreading memang teknologi yang sangat sulit ditangani secara intuitif. Ada banyak buku yang membahas race condition, deadlock, livelock, starvation, masalah visibilitas memori, dan sebagainya. Dibanding itu, pemrograman asinkron single-threaded jauh lebih ringan bebannya. Menanggung masalah warna fungsi adalah pilihan yang tidak terlalu menyakitkan dibanding men-debug "Heisenbug" di aplikasi multithreaded

    • Saya benar-benar senang Java mengambil pilihan itu

    • Penjelasannya adalah karena eksekusi normal dan fungsi asinkron membentuk closed Cartesian categories yang saling tertutup. Kategori eksekusi normal bisa di-embed langsung ke kategori asinkron. Semua fungsi punya kategori, yaitu warna fungsi, dan beberapa bahasa menampilkannya dengan lebih gamblang. Ini adalah pilihan desain bahasa, dan teori kategori bisa dimanfaatkan kuat melampaui threading. Java dan pendekatan berbasis thread akan berhadapan dengan masalah sinkronisasi, yang memang sangat sulit. JavaScript membatasi kategori monadik, khususnya pada gaya continuation-passing

  • Saat melihat contoh penggunaan using dengan fungsi defer, rasanya sangat segar. Mungkin bagi banyak orang lain ini sudah intuitif, tetapi menurut saya tetap layak disebutkan

    • Jika memanfaatkan DisposableStack dan AsyncDisposableStack yang termasuk dalam usulan using, pendaftaran callback didukung secara bawaan. Karena using bersifat block scope, ini diperlukan untuk melintasi scope atau pendaftaran kondisional. Namun variabel using harus langsung diinisialisasi seperti const, jadi tidak bisa diinisialisasi secara kondisional. Dalam kasus seperti ini dibutuhkan pola membuat Stack di level teratas fungsi lalu mendorong resource yang dipakai ke stack dengan defer. Jika perlu, waktu pelepasan resource juga bisa dengan mudah diubah ke level fungsi saja

    • Rasanya mirip golang

  • Menurut saya ini ide yang sangat bagus, tetapi meskipun penyatuan [Symbol.dispose] dan [Symbol.asyncDispose] mungkin dimungkinkan di masa depan untuk hal-hal seperti stream Web API, dalam waktu dekat hanya sebagian API dan library yang akan mendukungnya, sementara sisanya, kemungkinan besar mayoritas, tidak. Pada akhirnya muncul dilema antara mencampur using dengan try/catch, atau memakai try/catch di semua kode demi kode yang lebih mudah dipahami. Karena itu ada risiko fitur ini mendapat reputasi "tidak bisa dipakai secara praktis". Sangat disayangkan karena ini desain bagus yang benar-benar menyelesaikan masalah, tetapi mungkin sulit diadopsi

    • Untuk API yang tidak mendukung fitur seperti ini, DisposableStack bisa dipakai agar using tetap bisa diterapkan. Saat menangani beberapa resource sekaligus pun ini jauh lebih sederhana daripada try/catch. Selama runtime mendukungnya, ini bisa langsung dipakai tanpa harus menunggu resource lama diperbarui

    • Di dunia JavaScript, situasi seperti ini sudah berulang selama 15 tahun. Fitur bahasa baru biasanya lebih dulu masuk ke compiler seperti Babel, lalu ke spesifikasi, dan baru 3-4 tahun kemudian sering benar-benar hadir di API stabil dan browser. Para pengembang toh sudah terbiasa membungkus Web API dengan wrapper kecil, dan sering kali wrapper lebih baik daripada polyfill. Bahkan ketika ada fitur bahasa baru yang berguna, saya tidak pernah merasa "wah ini bakal susah dipakai"

    • Kenyataannya banyak fitur sudah diimplementasikan sebagai polyfill, jadi sebagian besar ekosistem NodeJS memakai pola ini, dan pengguna kadang hanya menyesuaikan sintaks lewat transpiler. Saat menyiapkan presentasi terkait tahun lalu, saya menemukan bahwa NodeJS dan library besar sudah punya cukup banyak API yang mendukung Symbol.dispose. Di frontend mungkin ini lebih jarang dipakai karena sudah ada sistem manajemen lifecycle, tetapi dalam beberapa situasi tetap berguna. Saya rasa ini akan cukup menyebar di library testing dan backend

    • TC39 juga perlu fokus pada fitur bahasa yang lebih mendasar seperti trait/protocol ala Rust. Di Rust, mendefinisikan dan mengimplementasikan trait baru relatif mudah, sementara di JS yang merupakan bahasa dinamis dan punya symbol unik, ini semestinya bisa diperkenalkan jauh lebih mudah. Memang ada kekurangan seperti orphan rule, tetapi strukturnya bisa berkembang jauh lebih fleksibel

    • Di dunia JavaScript, biasanya hal seperti ini diselesaikan dengan polyfill

  • Ini mengingatkan pada C#. Melalui IDisposable dan IAsyncDisposable, ini sangat berguna untuk abstraksi seperti pengelolaan lock, queue, dan scope sementara

    • Penulis usulan ini berasal dari Microsoft, jadi sintaksnya ditentukan mirip C#. Konteksnya juga konsisten di issue GitHub terkait

    • Pada dasarnya ini desain yang dipinjam dari C#. Usulan aslinya juga merujuk ke Python context manager, Java try-with-resources, dan C# using statement. Kata kunci using dan metode hook dispose memberi petunjuk yang sangat kuat

  • Saya paham JavaScript harus menjaga backward compatibility, tetapi sintaks [Symbol.dispose]() terasa canggung. Saya jadi bingung seolah array punya method handle. Saya penasaran ingin tahu lebih jauh sintaks ini sebenarnya apa

    • Dijelaskan bahwa di object literal, dynamic key yang dibungkus tanda kurung siku di sisi kiri sudah dipakai hampir 10 tahun sejak ES6. Selain itu symbol tidak bisa dirujuk dengan string, jadi dipakai kombinasi dynamic key dan shorthand method syntax. Menurut saya ini pada dasarnya bukan sintaks baru

    • Dengan referensi yang kuat, ini berasal dari cara menetapkan symbol key pada object yang sudah ada. Alurnya terasa alami

    • Pengguna lain sudah menjelaskan apa itu, tetapi tampaknya belum menjelaskan mengapa begitu. Dengan memakai Symbol untuk nama method, API baru dijamin tidak akan berbenturan dengan method yang sudah ada. Ini juga mencegah class secara tidak sengaja diperlakukan sebagai disposable

    • Disebut juga konsep dynamic property access. Properti objek bisa diakses dengan titik (.) atau kurung siku ([]), dan mendukung string maupun symbol. Symbol dibandingkan sebagai objek unik, dan well known symbol seperti Symbol.dispose menjamin ekstensibilitas. Dijelaskan juga bahwa ini mirip dengan metode __dunder__ di Python

    • Sintaks ini sudah dipakai bertahun-tahun. Iterator di JavaScript juga memakai cara yang sama, dan itu diperkenalkan hampir 10 tahun lalu

  • Diperkenalkan alasan mengapa mereka berupaya membawa structured concurrency ke JS, terutama saat lexical scope menjadi karakteristik penting dalam manajemen resource. Juga dibagikan library structured concurrency terkait

  • Bun sudah mendukung fitur ini sejak versi 1.0.23 ke atas. Bisa dicoba secara eksperimental

  • Saya benar-benar tidak mengerti bagaimana orang bisa memahami dan mengendalikan alur eksekusi program dengan gaya kode serumit ini

    • Itulah intinya. 90% pengembangan web adalah upgrade yang tidak berguna atau tidak diinginkan siapa pun, lalu menghabiskan 10% waktu lagi untuk memperbaiki masalah yang ditimbulkannya. Dengan peluang kecil, suatu hari seseorang harus membaca kode lama yang pernah ditulis, dan di situlah ide meninggalkan bug sebagai tugas pengantar untuk karyawan baru muncul. Bahkan sistem legacy berusia 20 tahun pun masih terus dipakai

    • Kode yang ditunjukkan sebagai contoh penuh dengan kesalahan sintaks serius sehingga jauh dari JavaScript yang nyata. Dan developer JS juga tidak mencampur pemakaian seperti itu, misalnya while, promise chain, finally, dan sebagainya, melainkan biasanya memakai await atau struktur penanganan eksepsi yang tepat. Dalam library yang dirancang baik, orang juga tidak menumpuk banyak lapisan handler, dan bisa menulis lebih ringkas dengan DisposableStack. Belakangan ini bahkan async IIFE pun sering tidak diperlukan lagi

    • Jika bekerja secara profesional dengan bahasa itu dan sudah terbiasa dengan arti serta perilaku kata kuncinya, kita akan memahami kode secara alami. Programmer Haskell juga terbiasa dengan hal serupa

    • Saat menyematkan kode di HN, setiap baris perlu diberi indentasi minimal 2 spasi. (Saya setuju bahwa kodenya memang sulit dipahami)

    • Saran singkat bahwa indentasi membantu

  • Saya penasaran kenapa tidak memakai destructor kelas anonim, atau struktur selain Symbol. Kalau ada dua Symbol, sinkron dan asinkron, itu memunculkan masalah kebocoran abstraksi

    • Destructor membutuhkan perilaku yang bisa diprediksi, yaitu cleanup yang jelas, tetapi garbage collector modern tidak cocok dengan pola seperti itu. Bahasa modern mendukung cleanup berbasis scope dan mengimplementasikannya lewat berbagai cara seperti HoF, hook khusus, atau pendaftaran callback. Python pada awalnya berbasis destructor dengan refcount GC, tetapi karena keterbatasannya akhirnya diperkenalkan context manager

    • Destructor di bahasa lain berjalan menurut timing GC sehingga sulit dipercaya. Sebaliknya, metode dispose dipanggil secara jelas saat scope variabel berakhir, sehingga lebih bisa diprediksi untuk menutup file atau melepas lock. Metode berbasis Symbol menghindari benturan dengan fitur yang sudah ada, dan biasanya cukup diperhatikan oleh pengembang library. Pembedaan sinkron dan asinkron memang perlu jelas, dan mungkin memerlukan sintaks yang sedikit asing seperti await using a = await b()

    • Di bahasa berbasis GC, destructor sulit dipanggil secara sinkron sehingga kebanyakan bersifat nondeterministik. Di JS ada WeakRef dan FinalizationRegistry, tetapi bahkan Mozilla pun tidak menganjurkan pemakaiannya karena tidak dapat diprediksi

    • Kelebihan pendekatan ini adalah bisa dipakai bukan hanya pada instance class

    • Di JavaScript tidak ada konsep properti anonim, jadi pertanyaannya sendiri terasa ambigu. Ada klaim bahwa selain cara ini memang tidak ada alternatif lain

  • Contoh pertama di dokumen usulan memperlihatkan kode yang aman melepas lock dengan try/finally. Saya jadi ingin tahu apakah pola seperti ini hanya penting untuk situasi yang berjalan lama, dan apakah di browser atau lingkungan CLI lock juga dilepas jika proses berhenti karena error

    • Di spesifikasi tertulis bahwa dispose akan selalu dijalankan, entah eksekusi blok berakhir normal, lewat eksepsi, percabangan, atau keluar dari blok. Jadi sama saja antara using dan try/finally. Penghentian paksa proses berada di luar cakupan spesifikasi, jadi ECMAScript tidak mengaturnya. Stream pada contoh adalah objek internal JS, jadi ketika interpreter hilang, konsep lock itu sendiri kehilangan makna. Jika yang dibahas resource OS seperti memori atau file, biasanya OS akan membersihkan semuanya sekaligus, tetapi perilakunya berbeda menurut platform

    • Halaman web browser, jika dilihat dari sisi lain, adalah aplikasi yang berjalan sangat lama. Bahkan bisa berjalan lebih lama daripada proses server. Saat error terjadi, halaman tidak serta-merta mati, dan penanganan error termasuk eksepsi punya aturan jelas untuk diproses di finally. Di NodeJS, secara default proses memang berakhir saat error, tetapi dalam situasi server sering ada penanganan lain. Artinya, fungsi pelepasan di finally tetap pasti dipanggil

 
ahwjdekf 2025-05-18

Selama ini kita baik-baik saja tanpa peduli sedikit pun soal resource. Kenapa kamu tiba-tiba jadi begini?