4 poin oleh GN⁺ 2026-02-28 | 1 komentar | Bagikan ke WhatsApp
  • Standar Web Streams dirancang untuk streaming data yang konsisten antara browser dan server, tetapi saat ini pengalaman pengembang menurun karena kompleksitas dan keterbatasan performa
  • API yang ada menimbulkan beban yang tidak perlu baik dalam penggunaan maupun implementasi karena batasan desain seperti pengelolaan lock, BYOB, dan backpressure
  • Cloudflare mengusulkan model stream baru berbasis iterasi asinkron (async iteration), dan pendekatan ini menunjukkan performa 2x hingga 120x lebih cepat
  • API baru meningkatkan efisiensi dan konsistensi melalui struktur async iterable yang sederhana, kebijakan backpressure yang eksplisit, dan dukungan jalur sinkron/asinkron secara bersamaan
  • Pendekatan ini memungkinkan model streaming terpadu di semua runtime seperti Node.js, Deno, Bun, dan browser, serta dapat menjadi titik awal untuk diskusi standar di masa depan

Keterbatasan struktural Web Streams

  • Standar WHATWG Streams dikembangkan pada 2014~2016 dan dirancang dengan fokus pada browser; saat itu async iteration belum ada, sehingga model reader/writer terpisah diperkenalkan
    • Akibatnya muncul prosedur yang tidak perlu seperti pengelolaan lock, loop pembacaan yang rumit, dan penanganan buffer BYOB
  • Model lock membuat stream dikuasai secara eksklusif sehingga mencegah konsumsi paralel, dan jika releaseLock() terlewat maka stream dapat terkunci permanen
  • Fitur BYOB (Bring Your Own Buffer) ditujukan untuk penggunaan ulang memori, tetapi model pemisahan dan transfer buffer yang rumit membuat pemakaian nyatanya rendah dan implementasinya sulit
  • Backpressure secara teori didukung, tetapi strukturnya tidak memungkinkan kontrol nyata, misalnya enqueue() tetap berhasil walau nilai desiredSize negatif
  • Setiap pemanggilan read() memaksa pembuatan Promise, sehingga pada streaming frekuensi tinggi hal ini menyebabkan penurunan performa dan beban GC

Masalah yang muncul di praktik

  • Jika body respons fetch() tidak dikonsumsi, dapat terjadi kehabisan connection pool, dan saat tee() digunakan muncul buffering memori tanpa batas
  • TransformStream langsung memproses tanpa memedulikan kesiapan baca, sehingga pada lingkungan dengan konsumen lambat dapat menyebabkan lonjakan buffer
  • Dalam server-side rendering (SSR), GC thrashing akibat pemrosesan ribuan chunk kecil membuat performa turun tajam
  • Untuk meredam hal ini, tiap runtime (Node.js, Deno, Bun, Workers) memperkenalkan jalur optimasi non-standar, tetapi akibatnya kompatibilitas dan konsistensi menurun
  • Web Platform Tests memerlukan lebih dari 70 file pengujian yang kompleks, dan ini merupakan hasil dari pengelolaan state internal yang berlebihan serta perilaku yang tidak intuitif

Prinsip desain Streams API baru

  • Stream didefinisikan sebagai async iterable sederhana, sehingga dapat langsung dikonsumsi dengan for await...of
  • Mengadopsi transformasi pull-through agar pemrosesan hanya dilakukan ketika konsumen meminta data
  • Menyediakan kebijakan backpressure eksplisit (strict, block, drop-oldest, drop-newest) untuk mencegah ledakan memori
  • Data dikirim dalam satuan chunk batch (Uint8Array[]) untuk mengurangi biaya pembuatan Promise
  • Disederhanakan menjadi pemrosesan khusus byte, dengan menghapus BYOB maupun konsep controller yang rumit
  • Dukungan jalur sinkron menghilangkan overhead Promise pada pekerjaan yang berfokus pada CPU

Contoh dan karakteristik API baru

  • Dengan Stream.push() dapat dibuat pasangan writer/readable secara sederhana, dan Stream.text() dapat mengumpulkan seluruh teks
  • Stream.pull() membentuk pipeline lazy yang hanya dieksekusi saat data dikonsumsi
  • Stream.share() dan Stream.broadcast() mendukung pengelolaan multi-konsumen yang eksplisit
  • API sinkron/asinkron paralel (Stream.pullSync(), Stream.textSync()) memaksimalkan performa pada operasi tanpa I/O
  • Demi interoperabilitas dengan Web Streams, konversi dimungkinkan melalui fungsi adapter sederhana

Perbandingan performa dan prospek

  • Dalam benchmark berbasis Node.js, terkonfirmasi kecepatan pemrosesan hingga 80~90x lebih cepat, dan di browser lebih dari 100x lebih cepat
    • Contoh: pada rantai transformasi 3 tahap, 275GB/s vs 3GB/s
  • Peningkatan performa berasal dari penghapusan overhead asinkron, pemrosesan batch, dan desain berbasis pull
  • Implementasi ini ditulis sepenuhnya dalam TypeScript/JavaScript, dan masih ada potensi peningkatan tambahan jika dibuat secara native
  • Cloudflare memosisikan pendekatan ini sebagai titik awal diskusi standar dan meminta masukan dari komunitas pengembang

Kesimpulan

  • Web Streams masuk akal dalam keterbatasan pada masanya, tetapi tidak lagi sesuai dengan fitur bahasa dan pola pengembangan JavaScript modern
  • Model baru berbasis async iterable memenuhi kesederhanaan, performa, dan kontrol eksplisit sekaligus, serta membuka kemungkinan ekosistem streaming yang konsisten lintas runtime
  • Cloudflare merilis implementasi referensi, dokumentasi, dan contoh kode di GitHub jasnell/new-streams
  • Tujuannya bukan menetapkan standar baru, melainkan menyediakan titik awal yang nyata untuk membahas “Streams API yang lebih baik”

1 komentar

 
GN⁺ 2026-02-28
Komentar Hacker News
  • Saya pernah merancang sendiri antarmuka Stream yang lebih baik daripada API yang diusulkan dalam tulisan ini
    Usulan yang ada berbentuk async iterator of UInt8Array, tetapi saya mengusulkan struktur di mana next() dapat mengembalikan hasil sinkron maupun asinkron
    Dengan begitu
    iterasi bisa dilakukan lebih sederhana dengan satu iterator dibanding struktur yang ada
    jika input sinkron diproses dengan transformasi sinkron, seluruh pemrosesan bisa tetap sinkron sehingga duplikasi kode berkurang
    pembuatan Promise yang tidak perlu berkurang sehingga performa meningkat
    kontrol konkurensi juga dimungkinkan sehingga keterbatasan async iterator bisa diatasi

    • Kamu bilang pendekatanmu lebih baik, tetapi sebenarnya menurut saya pendekatan pihak sana lebih unggul sebagai bentuk primitif yang lebih mendasar
      Dengan pendekatanmu, struktur mereka tidak mudah dibangun, sedangkan sebaliknya bisa
      iterator yang berpusat pada I/O harus mengembalikan chunk dalam satuan T agar pemborosan buffer bisa dicegah
    • Konsep stream yang kamu usulkan menarik, tetapi desain mereka berangkat dari asumsi kompatibilitas AsyncIterator
      Alasan menggunakan Uint8Array adalah agar selaras dengan byte stream tingkat OS
      Bahkan pada proyek berbasis C, struktur seperti ini memang paling efisien, jadi protokol dengan informasi tipe secara alami lebih cocok dibangun di atasnya
    • Di Node 24 saya mengukur perbedaan kecepatan antara pemanggilan fungsi sinkron dan fungsi async lewat microbenchmark, dan hasilnya sekitar 90 kali lebih lambat
      Pada versi lama, selisihnya bahkan sampai 105 kali
      Saya ingat ada optimisasi async di Node 16, dan saat itu beberapa pengujian sempat rusak
    • Tipe bernama Uint8Array itu memang ada
      Uint8Array hanyalah tipe primitif untuk merepresentasikan array byte, dan informasi tipe harus ditangani di level aplikasi, bukan level protokol
    • Struktur ini mirip dengan konsep transducer di Clojure
      Referensi: dokumentasi Clojure Transducers
  • Async iterable juga bukan solusi sempurna
    Overhead Promise dan perpindahan stack besar, sehingga performanya buruk saat menangani data berukuran kecil
    Di Lit-SSR, untuk mengatasi ini digunakan pendekatan memasukkan thunk ke dalam iterable sinkron
    thunk dipanggil dan di-await hanya ketika pekerjaan async benar-benar diperlukan, sehingga performa SSR meningkat 12 hingga 18 kali
    Namun Streams API sulit mengadopsi kontrak yang rapuh seperti ini, jadi menurut saya struktur seperti write() dan writeAsync() yang memungkinkan pemrosesan async opsional akan lebih ideal

    • Masalah yang kamu sebutkan bisa diselesaikan oleh stream iterator saya
      Saya membagikan contoh yang memanfaatkan generator sinkron di kode GitHub
      Intinya ada pada bagian step.value.then(value => this.next(value))
    • Saya suka usulan conartist6 (next(): {done, value: T} | Promise)
      Sejak perdebatan “Do not unleash Zalgo” pada 2013, ada kecenderungan menghindari bentuk MaybeAsync, tetapi
      menurut saya ketakutan ini terlalu dibesar-besarkan dan malah menghalangi desain API yang cepat dan fleksibel
      Kita juga bisa membuat utilitas untuk menarik banyak nilai sekaligus, dan menurut saya masalah kecepatan generator dalam praktiknya tidak terlalu besar
  • Menangani Web Streams di Node.js itu menyakitkan
    Karena dirancang dengan fokus browser, di lingkungan server terasa tidak nyaman
    Bahkan untuk transformasi sederhana pun kita harus membungkus transform stream, dan chaining intuitif seperti .pipe() sulit dilakukan
    Pendekatan async iterable jauh lebih alami dan cocok dengan for-await-of
    Spesifikasi Web Streams terlalu berorientasi abstraksi sehingga kurang praktis

    • Saya malah heran ada orang yang benar-benar memakai Web Streams di Node
      Saya pikir itu hanya untuk kompatibilitas antara klien dan server
  • Keuntungan sebenarnya bukan cuma performa, tetapi juga konsistensi lintas lingkungan (convergence)
    Jika ReadableStream berperilaku sama di browser, Worker, dan runtime lain
    portabilitas kode meningkat dan bug backpressure juga berkurang
    Standardisasi lapisan stream adalah kunci untuk membangun sistem streaming yang andal

    • Betul, nilainya bukan semata performa, melainkan nilai standardisasi
  • Dulu saya pernah membuat abstraksi bernama Repeater
    Konsepnya seperti memindahkan konstruktor Promise ke async iterable, dengan event dikendalikan lewat push/stop
    Pustaka Repeater cukup stabil sampai mencatat 6,5 juta unduhan per minggu
    Akhir-akhir ini saya lebih suka streams, tetapi kritik terkait tee() masih tetap valid
    Saya rasa arah yang tepat adalah menjadikan async iterable sebagai abstraksi dasar

    • Menarik bahwa stop di Repeater bertindak sebagai fungsi sekaligus Promise
      Setelah melihat kode sumber
      saya merasa meski berbeda dari pola tradisional, itu mungkin pilihan yang disengaja demi desain yang ergonomis
    • Ini memang di luar topik, tetapi contoh kode Konami terasa sangat menyenangkan untuk dilihat lagi
      Saya bahkan cukup bernostalgia sampai menulis “Up, Up, Down, Down, Left, Right, Left, Right, B, A” di tanda tangan email
  • Saya juga pernah membuat wrapper agar AsyncIterable bisa dipakai lebih ringkas
    Namanya fluent-async-iterator,
    dan itu berguna untuk streaming data skala kecil di Lambda atau pipeline CLI
    Saya berharap sekarang sudah ada API yang lebih baik

  • Perilaku backpressure pada ReadableStream.tee() membingungkan karena berlawanan dengan pipe() di Node.js
    Di spesifikasinya tertulis bahwa “output paling lambat harus menentukan lajunya”, tetapi implementasi nyata justru tersendat walau sisi yang cepat tidak dikonsumsi
    Menurut saya struktur ringkas berbasis push seperti Stream API baru akan lebih baik
    Node dan Web Streams memakai antrean tak terbatas sehingga res.write() bisa dipanggil terus secara sinkron, tetapi
    API ini memaksa alur yield berbasis generator sehingga lebih aman

  • Masalah habisnya connection pool saat memakai undici(fetch) di Node.js
    terjadi karena keterbatasan bahasa dengan garbage collection
    Jika resource tidak ditutup secara eksplisit, kebocoran bisa terjadi tergantung timing GC
    Pendekatan RAII (reference counting) di C++ justru lebih aman

  • Untuk pelepasan resource, saya berharap pola using/await using makin luas diadopsi
    Saya sedang menerapkan struktur yang mendukung dispose/disposeAsync seperti using di C# pada driver database

  • Angka benchmark (misalnya 530GB/s) sulit dipercaya karena melampaui bandwidth memori M1 Pro (200GB/s)
    Kemungkinan besar itu benchmark vibe-coded dengan kontrol kualitas implementasi yang buruk