3 poin oleh GN⁺ 2024-04-01 | 1 komentar | Bagikan ke WhatsApp
  • Proposal JavaScript Signals dari TC39 adalah arah awal untuk menstandarkan primitif reaktif guna melacak status UI dan status terhitung secara efisien, dan saat ini masih berupa draf level Stage 1
  • Proposal ini berfokus pada semantik inti graf Signal dan mekanisme pelacakan otomatis yang dapat dibagikan antar-framework, alih-alih API permukaan yang langsung digunakan pengembang aplikasi
  • Signal.State, Signal.Computed, dan Signal.subtle.Watcher adalah API inti, dengan komputasi yang menargetkan lazy evaluation, caching, pelacakan dependensi otomatis, dan eksekusi bebas glitch
  • Tujuan Signals bawaan adalah meningkatkan interoperabilitas antar-framework seperti Angular, Ember, MobX, Preact, Qwik, Solid, Svelte, Vue, dan lainnya, sekaligus membuka kemungkinan dukungan debugging dan analisis performa di DevTools
  • Kelompok proposal berencana melewati berbagai polyfill kelas produksi, integrasi framework, validasi pada aplikasi berskala besar, dan benchmark performa sebelum Stage 2, dan standardisasi kemungkinan memerlukan setidaknya 2~3 tahun atau lebih

Posisi proposal JavaScript Signals

  • JavaScript Signals diperkenalkan sebagai proposal Stage 1 dalam proses TC39
  • Dokumen saat ini adalah upaya untuk menyelaraskan arah bersama di ekosistem JavaScript, mirip dengan posisi Promises/A+ sebelum standardisasi Promise di ES2015
  • Tersedia polyfill yang bisa langsung dicoba
  • Champion proposal dan penulis asli menyusun draf saat ini berdasarkan masukan desain dari berbagai framework dan library

Masalah yang dibidik oleh standardisasi

  • UI yang kompleks perlu menyimpan nilai, menghitung, menginvaliasi, menyinkronkan, dan mendorongnya ke lapisan view, dan Signals berupaya menyediakan infrastruktur manajemen status untuk pekerjaan berulang semacam ini
  • Dalam contoh Vanilla JS, counter, isEven, parity, dan render saling terikat langsung sehingga menimbulkan masalah berikut
    • Status dan sistem rendering menjadi sangat tightly coupled
    • Bahkan ketika parity tidak berubah, seperti saat counter berubah dari 2 ke 4, komputasi dan rendering yang tidak perlu tetap terjadi
    • Saat bagian UI lain ingin berlangganan hanya ke sebagian dari counter, isEven, atau parity, pengelolaan subscribe dan unsubscribe manual menjadi rumit
    • Jika pub/sub ditambahkan ke banyak tahap, boilerplate dan bookkeeping subscription meningkat serta risiko memory leak pun muncul
  • Contoh berbasis Signals menangani nilai, komputasi, dan efek samping dengan satu cara menggunakan Signal.State dan Signal.Computed
    • Tidak perlu subscription manual
    • Signal terhitung secara otomatis menemukan Signal yang menjadi dependensinya
    • Komputasi hanya dijalankan saat nilainya diminta secara eksplisit
    • Signal terhitung menyimpan cache nilai terakhir

API inti proposal

  • Signal<T> didefinisikan sebagai nilai yang dapat dibaca dengan get(): T
  • Signal.State<T> adalah Signal yang dapat ditulis
    • Konstruktor menerima nilai awal dan opsi
    • Nilai dibaca dengan get() dan diubah dengan set(t)
  • Signal.Computed<T> adalah Signal terhitung yang berbasis pada Signal lain
    • Dihitung dari nilai yang dikembalikan callback
    • Melacak dependensi secara otomatis
    • Nilainya dihitung secara lazy dan disimpan dalam cache
  • Signal.subtle memuat API tingkat lanjut yang lebih dekat ke penulis framework atau implementasi DevTools
    • untrack(cb) membaca Signal tanpa pelacakan
    • currentComputed() mengembalikan computed Signal yang sedang dilacak saat ini
    • introspectSources, introspectSinks, hasSinks, hasSources adalah API untuk mengamati graf
    • Watcher adalah fondasi untuk mendeteksi perubahan Signal dan mengimplementasikan effect serta scheduling di level framework
  • SignalOptions<T> mendukung fungsi pembanding kustom equals serta hook watched / unwatched

Cara kerja dan model eksekusi

  • Signal merepresentasikan sel data yang dapat berubah seiring waktu, dan dibagi menjadi state atau computed
  • Signal terhitung secara otomatis mencatat Signal yang dibaca saat eksekusi, lalu saat dibaca lagi memeriksa apakah dependensi sebelumnya telah berubah
  • Komputasi bersifat pull-based
    • Tidak langsung dihitung ulang ketika dependensi berubah
    • Dihitung ulang hanya bila perlu saat seseorang membacanya lewat .get()
  • Penulisan ke State Signal tercermin secara sinkron
    • Setelah .set(), jika computed Signal yang bergantung padanya dibaca, ia akan langsung dihitung ulang bila perlu
    • Tidak ada batching bawaan
  • Callback notify milik Watcher dapat dijalankan secara sinkron selama .set()
    • Namun Signal tidak boleh dibaca atau ditulis selama notify
    • Operasi baca/tulis yang sebenarnya harus dijadwalkan setelahnya
  • Jika callback computed Signal melempar exception, exception tersebut juga disimpan dalam cache seperti nilai, dan akan dilempar lagi saat Signal dibaca kembali

Motivasi standardisasi

  • Implementasi Signal di tiap framework memiliki mekanisme pelacakan otomatisnya sendiri, sehingga model, komponen, dan library sulit dibagikan antar-framework
  • Tujuan proposal ini adalah memisahkan model reaktif dari view rendering
    • Agar pengembang tidak perlu menulis ulang kode non-UI meskipun teknologi rendering diganti
    • Agar model reaktif yang dapat dibagikan di berbagai konteks bisa dibuat di JavaScript
  • Dari sisi performa dan memori, implementasi bawaan mungkin lebih efisien daripada implementasi JS dengan faktor konstan yang lebih kecil, tetapi dijelaskan bahwa engine tidak akan secara ajaib mengubah algoritmenya
  • Dari sisi DevTools, Signals bawaan dapat menampilkan informasi berikut dengan lebih baik
    • call stack dari rantai computed Signal
    • graf referensi antar-Signal
    • hubungan dependensi yang diperlukan untuk debugging penggunaan memori
  • Jika dimasukkan ke pustaka standar, efek samping positif yang diharapkan juga mencakup pengurangan ukuran bundle, peningkatan stabilitas dan kualitas, serta pembentukan kosakata bersama antar-proyek

Tujuan dan batasan desain

  • Fitur inti mencakup Signal yang dapat ditulis, Signal terhitung, reaksi terhadap status dirty, scheduling framework itu sendiri, untrack, dan komposisi lintas berbagai codebase
  • Signal terhitung menargetkan eksekusi bebas glitch
    • Untuk menghindari komputasi yang tidak perlu, bagian graf yang berpotensi dirty dijalankan dengan pengurutan topologis
    • Komputasi duplikat diupayakan untuk dihilangkan
  • Tidak disertakan scheduling bawaan yang dipaksakan ala Promise agar framework bisa melakukan scheduling sendiri
  • Untuk mencegah penyalahgunaan callback reaksi sinkron, pembacaan dan penulisan Signal dilarang di dalam notify milik Watcher
  • untrack diperlakukan sebagai jalur keluar yang tidak aman
    • Jika Signal yang dibaca tanpa pelacakan memengaruhi hasil komputasi, maka saat Signal itu berubah, computed Signal mungkin tidak ikut diperbarui
  • API ini diprioritaskan sebagai fondasi implementasi framework, dan tidak secara khusus dirancang agar ergonomis bagi pengembang aplikasi umum

effect dan Watcher

  • Proposal ini tidak memiliki fungsi bawaan seperti effect()
  • Scheduling effect terkait erat dengan siklus rendering framework, disposal, dan manajemen kepemilikan, sehingga API standar JavaScript tidak menanganinya secara langsung
  • Sebagai gantinya, Signal.subtle.Watcher menyediakan fondasi tingkat rendah untuk implementasi effect
    • notify dipanggil ketika dependensi dari Signal yang diawasi berubah
    • getPending() dapat digunakan untuk memeriksa Signal yang masih dirty
    • Effect yang perlu di-dispose harus dibersihkan dengan unwatch
  • Signal yang sedang dipantau oleh Watcher dapat tetap hidup selama state internalnya masih dapat dijangkau, sehingga saat membersihkan effect diperlukan pemanggilan Watcher.prototype.unwatch

Fitur yang belum masuk dalam draf saat ini

  • Async belum termasuk dalam model saat ini
    • Signals selalu diperlakukan sebagai nilai yang dapat dievaluasi secara sinkron
    • Ada sebagian cara untuk memodelkan status loading sebagai exception, dan pembahasan perbaikannya ada di Issue #30
  • Transactions juga belum termasuk
    • Untuk mempertahankan status “from” dan “to” secara bersamaan dalam transisi layar, muncul masalah forking pada status graf Signal
    • Pembahasan terkait ada di Issue #73
  • Beberapa convenience methods juga belum ada dalam draf saat ini
  • Fitur-fitur yang belum masuk dikecualikan karena kurangnya konsensus antar-framework dan karena masih bisa diakali di lapisan yang lebih tinggi, tetapi dapat ditinjau ulang setelah prototipe tersedia

Rencana pengembangan dan jadwal standardisasi

  • Proposal ini masuk ke agenda TC39 Stage 1 pada April 2024, tetapi dijelaskan bahwa secara dokumen saat ini juga bisa dipandang seperti Stage 0
  • Sebelum mengusulkan Stage 2, pekerjaan berikut direncanakan
    • Mengembangkan beberapa implementasi polyfill kelas produksi
    • Lulus berbagai pengujian framework dan pengujian bergaya test262
    • Memverifikasi performa melalui benchmark set signal/framework yang menyeluruh
    • Mengintegrasikan API proposal ke berbagai framework JS representatif dan sebagian aplikasi skala besar
    • Memahami kemungkinan perluasan API dan memutuskan apakah akan disertakan
  • Kelompok proposal ingin bergerak secara konservatif agar bentuk Signals yang keliru tidak terlalu cepat distandardisasi
  • FAQ memperkirakan bahwa Signals standar akan membutuhkan setidaknya 2~3 tahun sebelum dapat digunakan secara luas di browser tanpa polyfill
  • Polyfill saat ini sudah bisa digunakan, tetapi disarankan untuk tidak bergantung pada kestabilannya karena API dapat berubah selama proses peninjauan

Model penggunaan yang dirangkum di FAQ

  • Signals bawaan bersifat independen dari teknologi rendering
    • Dijelaskan bahwa semuanya memungkinkan, baik Preact yang memakai VDOM, Solid yang memakai native DOM, maupun Vue yang memakai pendekatan campuran
  • Untuk pengembang aplikasi, penggunaan Signals melalui framework pada umumnya lebih cocok
    • Framework mengelola Watcher, untrack, ownership, disposal, dan scheduling rendering DOM
  • Dapat digunakan bersama SSR, hydration, dan resumability
    • Qwik sudah menggunakan Signals bersama karakteristik ini, dan pihak proposal menilai bahwa resumable Signals milik Qwik dapat dimodelkan dengan kombinasi State dan Computed
  • Signals dan Proxy saling melengkapi
    • Proxy mencegat operasi objek dangkal, sementara Signals mengoordinasikan graf dependensi sel data
    • Menempatkan Signals di balik Proxy dapat membuat struktur reaktif bertingkat menjadi lebih ergonomis
  • Signals bukan stream, melainkan sel yang merepresentasikan nilai saat ini
    • Jika dua kali penulisan dilakukan berturut-turut ke State Signal lalu tidak ada tindakan lain, penulisan pertama mungkin tidak akan terlihat oleh computed Signal atau effect
    • Dokumen menyebut ini sebagai karakteristik di sisi berlawanan dari eksekusi bebas glitch, dan menyatakan bahwa untuk stream, konstruksi lain seperti async iterable atau observable lebih cocok

1 komentar

 
GN⁺ 2024-04-01
Komentar Hacker News
  • Apakah hanya saya yang merasa contoh JavaScript murni justru lebih mudah dibaca dan ditangani?
    Katanya “konfigurasinya berisik dan banyak boilerplate”, tetapi contoh signals juga tampak sama berisik dan penuh boilerplate, bahkan menambahkan konsep baru yang sulit dipahami pemula
    Pernyataan bahwa “ketika counter berubah dari 2 ke 4, parity tidak berubah tetapi tetap melakukan komputasi dan rendering yang tidak perlu” terdengar seperti memoization prematur
    Kalau bagian lain dari UI ingin me-render mengikuti pembaruan counter, benar bahwa contoh strawman itu tidak tepat, dan saat itu bisa memakai cara lain seperti signals, penanganan event, atau penyimpanan state terpusat (semacam Redux)
    Kalau bagian lain dari UI hanya bergantung pada isEven atau parity, pendekatannya bisa diubah jika itu struktur inti aplikasi, tetapi kebanyakan tidak begitu. “Harus tahu bahwa fungsi render yang hanya bergantung pada parity perlu berlangganan ke counter” juga belum tentu beban yang tidak adil, dan fungsi komputasi murni punya keunggulan karena input-nya mudah dipahami

    • Saya tidak mengerti mengapa ini dianggap memoization prematur. Ini hanya contoh yang diperkecil menjadi fungsi sederhana, dan sulit membayangkan orang-orang mengarang use case seperti ini tanpa pernah benar-benar membutuhkannya
      Upaya menstandarkan signals, sebuah konsep yang makin banyak dipakai dalam pengembangan UI, patut diapresiasi. Terlepas dari perdebatan detail seperti seberapa banyak boilerplate-nya atau apakah harus membuat sistem event sendiri, jika banyak framework memakai signals, mungkin ada alasannya, dan layak dicoba untuk distandardisasi meski memakan waktu
    • Setuju. Namun jika melihat dokumentasi signal milik Preact, konteksnya jauh lebih pas
      https://preactjs.com/guide/v10/signals
      Di Preact, ketika signal turun melalui tree sebagai props atau context, yang diteruskan hanya referensi signal, dan karena komponen melihat signal, bukan nilainya, memperbarui signal bisa tidak me-render ulang komponen. Pada praktiknya, ini bisa langsung menuju komponen di tree yang mengakses .value
      Selain itu, signal melacak kapan nilai diakses dan kapan diperbarui, dan di Preact, jika .value dari sebuah signal diakses di dalam komponen, komponen itu otomatis di-render ulang ketika nilai signal tersebut berubah
    • Karena reaktivitas tidak tertanam di JavaScript, menambahkan reaktivitas pasti menimbulkan biaya abstraksi. Ini untuk dipakai saat dibutuhkan, bukan harus menjadi cara dasar menangani state
      Dari pengalaman, keuntungan besarnya adalah bisa memodularisasi state reaktif. Dalam gaya imperatif, state tambahan diperlukan untuk melacak perubahan, dan modularitas dicapai lewat abstraksi. Gunakan saja saat diperlukan
      Membuat contoh yang sederhana sekaligus dapat diterapkan adalah soal keseimbangan. Kasus ketika reaktivitas jelas menguntungkan biasanya lebih kompleks, sehingga lebih sulit ditampilkan dibanding contoh yang sederhana tetapi kurang dapat diterapkan
    • Cara menjelaskan ini masih bisa diperbaiki. Pada contoh kecil, masalahnya tidak terlalu terlihat, dan baru muncul pada skala yang lebih besar. PR dipersilakan
    • Layak dihindari ketika melewati ambang kompleksitas tertentu membuat perubahan desain menjadi perlu. Pendekatan JS murni punya batas skalabilitas dari sisi kompleksitas grafik state, dan masalah sebenarnya bukan kegunaan sebelum atau sesudah ambang itu, melainkan kegunaan berubah secara diskontinu begitu ambang tersebut terlampaui
  • Ketika Promises ditambahkan ke JavaScript, saya sempat enggan karena khawatir harus menulis new Promise di mana-mana
    Kenyataannya, jumlah kali saya menulis new Promise langsung bisa dihitung dengan dua tangan. Sebaliknya, saya jauh lebih sering memakai .then, terutama saat menangani library pihak ketiga
    Pada akhirnya, dampak sehari-hari Promise yang ditambahkan ke JavaScript adalah memberi antarmuka yang cukup sederhana, umumnya kokoh, dan hampir universal untuk berbagai perilaku dan fungsi khusus yang disediakan library pihak ketiga. Entah membaca file, request API, atau output tahap build, memakai .then(res => …) membuat rasanya sudah setengah jalan menuju sesuatu yang bisa berjalan
    Jika proposal Signal ini bisa memainkan peran serupa di tengah ledakan ala Kambrium framework UI reaktif, saya mendukungnya. Lebih jauh lagi, ini juga bisa membantu reaktivitas meluas ke luar UI. Saya sering membayangkan tree state rekalkulasi bertahap untuk hal-hal yang bukan state UI

    • Saya memandang Promises terutama masuk untuk menambahkan async/await, dan yang benar-benar meningkatkan kualitas hidup secara besar adalah itu. Dalam praktik, jarang sekali perlu menulis new Promise langsung
      .then pada Promise awal adalah peningkatan besar dibanding delegate bertingkat dan cukup baik untuk chain sederhana, tetapi ketika perlu men-chain Promise yang berbeda secara kondisional, menangani error secara berbeda pada chain tertentu, atau melakukan return lebih awal, kodenya bisa menjadi jauh lebih sulit dibaca dan ditangani
      Dengan async/await, pemanggilan bisa ditulis seolah-olah bukan Promise, try/catch bisa dengan mudah diletakkan di sekitar pemanggilan Promise tertentu, dan return lebih awal juga terasa alami
  • Saya tidak paham mengapa ini harus menjadi bagian dari bahasa. Ini bisa dilakukan sebagai library, dan library seperti itu sudah ada. Karena kecil, memasukkannya ke dalam kode tidak terlalu membebani, dan menambahkannya ke bahasa tidak boleh menjadi tujuan itu sendiri
    Menganggap bahwa library UI JS saat ini telah merancang signals dengan sangat baik hingga harus menjadi bagian dari bahasa adalah sikap arogan. Ada banyak implementasi signals dengan kompromi yang berbeda-beda, dan tidak satu pun dari mereka layak mendapat tempat khusus dalam spesifikasi JavaScript
    Sebelum memakai signals, library-library seperti ini menggunakan virtual DOM. Untungnya virtual DOM tidak menjadi bagian dari JS, lalu apa bedanya signals? Tidak ada. Argumen untuk menstandarkannya bahkan lebih lemah dibanding saat virtual DOM
    Apakah kita akan menumpuk semua hal yang sedang tren ke dalam runtime yang pada dasarnya tidak punya cara untuk menghapus fitur yang kelak tidak lagi diinginkan tanpa merusak web? Itu cukup rabun

    • Ada benarnya. Kita tidak menginginkan hal yang salah, tetapi kita menginginkan hal yang benar
      UI reaktif sudah menang. Bahkan pada aplikasi kecil, ketika mengelola state, kompleksitas bisa meledak, dan itulah inti yang membuat JS murni sulit digunakan. Bagi saya, framework reaktif apa pun lebih baik daripada JS murni, jadi mungkin memang ada komponen yang hilang
      Sekarang sudah sekitar 10 tahun berlalu, sudah saatnya memikirkan batas yang bisa distandarkan. Jika dilakukan dengan benar seperti Promise, ini bisa menurunkan kompleksitas untuk use case yang sangat umum
      Penilaian yang lebih baik adalah bertanya, “Apakah framework reaktif yang ada akan memakai proposal ini?” Jika tidak, perlu dilihat mengapa, apa yang kurang, apa yang tidak perlu, dan apa yang bisa dipelajari dari UI serta reaktivitas di bahasa lain. Pengalaman yang tersebar layak disaring
    • Salah satu alasan bagus untuk menstandarkan Signals adalah debugging tampak seperti mimpi buruk. Bayangkan pohon signal terhitung yang dalam saling memicu secara berantai, dan Anda harus menemukan titik awal reaksi berantai itu. Jika distandarkan, developer tools bisa dibangun di sekitarnya
    • Hal yang sama bisa dikatakan untuk sebagian besar standard library. Namun seperti disebutkan dalam motivasinya, ada arus untuk memperluas standard library JS yang relatif kecil agar tidak perlu mengimpor paket untuk setiap pekerjaan umum
      Kebutuhan itu bisa diperdebatkan, tetapi jika standard library akan diperluas, melihat hal-hal yang populer menurut saya adalah pendekatan yang baik
      signals bukan pengganti virtual DOM
    • Saya teringat proposal Observable
  • Saat perlu memberi sinyal tentang sesuatu ke seluruh aplikasi, kita memakai event
    window.dispatchEvent(new Event('counterChange'));
    Lalu bagian mana pun dari aplikasi yang ingin bereaksi bisa berlangganan seperti ini
    window.addEventListener('counterChange', () => { ... do something ... });
    Apa yang salah dengan cara ini?

    • Secara historis, contoh inilah alasan web berevolusi menjadi jQuery, lalu dari sana terbelah ke dunia Angular dan React
      Penanganan event sangat mudah menjadi berantakan. Untuk melihat lebih dalam, lihat event bubbling dan propagation
      Aplikasi besar membutuhkan penanganan event yang kokoh, dan inilah keunggulan framework seperti Angular dan Vue yang sekarang tidak terlalu terlihat
      Anda mungkin tidak ingin memakai API penanganan event standar begitu saja tanpa framework. Saat menangani penambahan, penghapusan, duplikasi, pemicuan, pembersihan, pemicuan satu kali, dan sebagainya untuk banyak elemen, efek samping tak diinginkan yang serius bisa muncul
    • Menurut artikel, event emitter/observable menimbulkan pekerjaan yang tidak perlu ketika dipanggil berkali-kali
      Perbedaannya dengan signals adalah nilai hasil baru dihitung hanya ketika konsumen akhir membaca nilainya. Waktu penulisan aktual ke signal dipisahkan dari penjadwalan pembaruan render asinkron, dan rantai komputasi yang dilakukan watcher hanya dijalankan sekali selama render
      Nilai-nilai perantara yang dikirim lewat signal akan hilang, sehingga sulit melakukan banyak hal menarik di dalamnya; pada dasarnya ini lebih dekat ke lapisan abstraksi tingkat tinggi untuk mengoordinasikan siklus rendering
    • Signals pada akhirnya juga publish/subscribe, tetapi API-nya lebih enak digunakan. Sebab listener ditambahkan dan dilepas secara otomatis
      Performanya juga bisa lebih baik. Misalnya ada komputasi yang bergantung pada dua nilai: pada result = a ? b : 0, jika a bernilai false, perubahan b tidak perlu memicu perhitungan ulang. Di signals, ini terjadi otomatis, sedangkan dengan publish/subscribe tradisional butuh cukup banyak kode
    • Saya sudah memakai pola seperti ini lebih dari 10 tahun. Bagian sulitnya adalah seiring waktu, satu listener bisa memicu event lain, lalu event lain itu kembali ke rutinitas pertama sehingga terbentuk loop listener yang tidak pernah berakhir
      Sulit juga menjamin bahwa semua listener tidak membuat pemicuan berantai seperti itu
    • Cara itu memiliki semua kelemahan arsitektur publish/subscribe yang ditekankan dalam proposal
  • Selama puluhan tahun, orang-orang berusaha memahami mengapa pelacakan state dan pembaruan DOM terasa begitu sulit
    Tentu perlu sedikit disiplin, tetapi rasanya jauh lebih sederhana dibandingkan solusi-solusi yang muncul setiap beberapa tahun. Backbone, Knockout, Angular, React, modifikasi bahasa itu sendiri, dan sebagainya; mungkin cara berpikirku memang pada dasarnya berbeda
    Bahkan terlihat dari nama fungsinya. Memperbarui innerText disebut “render”, padahal sebenarnya bukan merender. Paling-paling browser yang merender, dan itu juga berlaku untuk semua pekerjaan lain yang terkait painting. Rasanya seperti upaya putus asa untuk membuat salah satu fungsi DOM paling sederhana menjadi rumit, jadi benar-benar membingungkan

    • Mudah untuk aplikasi sederhana
      Kalau makin kompleks, tidak mudah
    • Dari ungkapan “perlu sedikit disiplin”, saya mendapat kesan kuat bahwa dulu Anda mungkin akan marah sebagai programmer ASM kepada para programmer C portabel yang gila, lalu kemudian sebagai programmer C marah kepada para programmer Java yang gila dengan keamanan memorinya
      Kemajuan pemrograman bisa dilihat sebagai proses menghilangkan ritual dan disiplin ketat yang diperlukan untuk mendapatkan hasil yang baik
      Bukan berarti React adalah evolusi berikutnya, tetapi signals jelas merupakan satu langkah ke arah yang benar
    • Puluhan tahun itu waktu yang panjang. Anda pasti ingat betapa rumitnya pembaruan DOM di tiap browser
      Menyinkronkan DOM dengan state data itu sendiri tidak terlalu sulit, tetapi melakukannya dengan performa sangat baik pada 60fps luar biasa sulit. Terutama saat membuat API yang tidak bocor tetapi juga tidak terlalu merepotkan
      Sejujurnya, menggambar piksel di canvas seperti game bisa jadi lebih mudah daripada menerjemahkan perubahan lalu mencerminkannya ke pohon DOM yang hidup
    • Saya sedang membuat aplikasi trading FX berskala ratusan ribu baris. Ini setara menggantikan aplikasi desktop tebal, dengan 20–30 developer dan beberapa pelanggan yang masing-masing punya developer sendiri. Kalau diminta melakukannya tanpa framework, saya hanya bisa mendoakan semoga beruntung
    • Sepenuhnya sependapat. Saya juga pernah mengembangkan SPA yang sangat kompleks dan sangat interaktif, tetapi saya belum pernah menemui masalah yang katanya diselesaikan oleh hal-hal seperti ini
  • Promises adalah contoh sukses yang bagus, tetapi tanpa async/await, belum tentu perlu distandardisasi
    Draf saat ini katanya didasarkan pada desain dari para pembuat/pemelihara Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz, dan lainnya; saya penasaran bagaimana para pembuat library yang sudah ada melihat proposal ini. Menarik juga bahwa React tidak ada dalam daftar
    Signals agak mirip channel, tetapi bedanya ini broadcast, bukan penerima tunggal. Akan keren jika ini dimanfaatkan agar web worker bisa berkomunikasi lewat channel alih-alih callback onMessage. Terutama jika, seperti Go, kita bisa melakukan select di atas signals/channels/promises; secara sintaksis itu punya keunggulan dibanding mengelola beberapa mekanisme messaging konkuren dengan callback. Misalnya dengan memungkinkan signals disertakan dalam Promise.any

    • Saya sangat tidak setuju dengan pernyataan bahwa “tanpa async/await, tidak perlu distandardisasi”
      x instanceof Promise sama sekali tidak berfungsi begitu saja. Jika metode then di library saya menerima callback catch tetapi library lain tidak, keduanya diam-diam tidak interoperabel dan tidak ada cara untuk mendeteksinya. Kapan finally dijalankan? Ekspektasi apa yang bisa kita punya tentang seberapa asinkron callback akan dieksekusi?
      Tanpa standar, setiap library yang menggunakan Promise harus membawa polyfill-nya sendiri. Karena kita tidak bisa mempercayai yang sudah ada. Dan Promise dari library lain pun tidak benar-benar bisa dikonsumsi. Karena kita tidak bisa yakin ia akan berperilaku sesuai harapan
      Ini bukan dugaan; selama bertahun-tahun memang begitulah keadaannya, dan itu adalah neraka yang harus ditanggung banyak orang
    • Ada juga manfaat standardisasi yang tidak terkait async/await. Engine JavaScript bisa melakukan optimisasi performa yang menguntungkan aplikasi yang banyak menggunakan Promise, dan itu tidak akan mungkin jika tidak ada standar
    • Alasan React tidak ada dalam daftar adalah karena signals, berbeda dengan Preact, bukan bagian dari API inti React
      Intuisi samar saya, signals terlalu mirip dengan useEffect() yang digeneralisasi, sehingga jika masuk ke React, itu akan membuat apa yang terjadi selama siklus render menjadi lebih membingungkan. Baik atau buruk, React memilih cara pembaruan yang berbeda dari signals. Namun saya bisa saja keliru soal kemungkinan penerapannya
    • Alasan React tidak ada dalam daftar ini adalah karena efeknya bersifat deklaratif, bukan imperatif. Perubahan props dan render ulang juga bisa dilihat sebagai deklaratif pada satu tingkat abstraksi di atasnya. useEffect mengisolasi perilaku imperatif dengan rapi
      Ini terlihat sangat mirip dengan data binding Ember, dan pada akhirnya bisa menjadi mimpi buruk imperatif. Keadaan default-nya nyaris seperti “senjata yang menembak kaki sendiri”, dan untuk mencegahnya menjadi seperti itu dibutuhkan beban kognitif dan meta-pattern yang sangat besar
    • Mungkin ini cukup disebut “EventEmitter”
      https://nodejs.org/en/learn/asynchronous-work/the-nodejs-eve...
  • Tidak memahami contoh di README yang ditautkan
    // A library or framework defines effects based on other Signal primitives
    declare function effect(cb: () => void): (() => void);
    Library apa? Framework apa? Saya tersesat di sini. Apa itu effect?
    effect(() => element.innerText = parity.get());
    Bagaimana effect tahu bahwa ia harus memanggil lambda ini setiap kali parity berubah? Apakah ia memanggil lambda ini setiap kali signal berubah? Kalau begitu, mengapa membahas caching? Mungkin bukan begitu
    Bagaimanapun, jika saya memahami dengan benar apa yang ingin disampaikan para penulis, ide signal itu sendiri terlihat masuk akal. Namun masalah besar dari arsitektur terpisah seperti ini adalah, ketika aplikasi menjadi cukup kompleks, kita akan tersesat saat mencoba melacak mengapa event tertentu terjadi. Idealnya, signals harus memperbaiki stack trace sehingga saat callback dipanggil, stack trace dari kode yang sejak awal memicu signal sudah ikut disertakan

    • Ada beberapa library yang mengekspor fungsi bernama effect, dan memungkinkan kode arbitrer dijalankan sebagai respons terhadap update signal. Pengantar signals dan effects di dokumentasi Preact bagus: https://preactjs.com/guide/v10/signals#effectfn
      Sejauh yang saya pahami, fungsi effect semacam ini pertama-tama menjalankan callback sekali untuk mengetahui signal apa saja yang diakses selama eksekusi, lalu memanggil ulang callback setiap kali signal yang menjadi dependensi callback itu diperbarui. Jika akses signal bersifat sinkron dan single-threaded, fakta bahwa sebuah signal diakses saat callback berjalan sudah cukup untuk mengetahui bahwa callback tersebut harus berlangganan ke signal itu
      Ini juga bisa dilakukan dengan getter. Fungsi effect melacak properti signal mana yang diakses di dalam metode getter, dan setahu saya Vue 2 dulu memakai pendekatan seperti ini. Akses objek juga bisa dilacak dengan proxy. Contoh dalam proposal memiliki metode get yang dipanggil untuk mengakses nilai signal, dan dependensi dapat dilacak melalui eksekusi metode ini
      [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
      [2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
    • Pemanggilan parity.get() mendaftarkan dependensi untuk fungsi yang diteruskan ke effect(). Jika parity diperbarui, fungsi itu akan dipanggil
      Bukan dipanggil setiap kali signal berubah, melainkan hanya saat signal yang menjadi dependensinya berubah
      Dalam kasus ini parity bergantung pada isEven, dan isEven bergantung pada counter. Jadi ketika counter diperbarui, seluruh rantai dependensi diinvalidasi, parity menjadi invalid, dan callback dijalankan lagi
    • Implementasi signals, apa pun namanya, umumnya membuat graf dependensi dinamis, dan edge terbentuk saat node dibaca. Dalam konteks pelacakan seperti effect hipotetis ini, pembacaan membuat edge antara node status signal dan node komputasi effect, yang pada dasarnya membuat yang terakhir berlangganan ke penulisan berikutnya pada yang pertama, sehingga sistem dapat menentukan kapan komputasi perlu dijalankan ulang
    • effect adalah fungsi arbitrer yang ingin Anda panggil
      Dalam signals, mekanisme pelacakan dependensi mengetahui nilai mana yang perlu dihitung ulang, dan sebagai hasilnya sistem juga mengetahui fungsi mana yang harus dipanggil ulang
    • Sepertinya watcher akan diperlukan untuk implementasi effect
  • Terkait ini ada S.js: https://github.com/adamhaile/s
    Saya suka signals. Saat membuat UI, saya lebih memilihnya daripada primitive lain mana pun, mungkin dengan pengecualian algoritma constraint cassowary. Saya mencoba meniru signals di semua bahasa yang saya pakai untuk bersenang-senang
    Namun saya sama sekali tidak berpikir ini sesuatu yang harus masuk ke bahasa JavaScript itu sendiri. Saya berharap bahasa ini dibiarkan saja untuk sementara. Orang-orang sudah kesulitan mengikutinya, dan TC-39 sudah membuat orang merasa terintimidasi lalu menjauh dari bahasa ini

  • Ini terlihat sangat mirip dengan sistem effect JS favorit saya, MobX
    Versi MobX seperti ini
    import { observable, computed, autorun } from 'mobx';
    const counter = observable.box(0);
    const isEven = computed(() => (counter.get() & 1) === 0);
    const parity = computed(() => isEven.get() ? "even" : "odd");
    autorun(() => { element.innerText = parity.get(); });
    setInterval(() => counter.set(counter.get() + 1), 1000);

    • MobX memang signals. Hanya saja, alih-alih melacak dependensi secara eksplisit dengan getter, ia adalah signals yang melacaknya secara implisit melalui objek proxy
  • Rasanya seperti “ayo masukkan framework yang sedang kupakai belakangan ini ke standard library!”
    Mirip seperti menato nama pacar di tubuh

    • Ini bukan seperti itu
      Ini memasukkan komponen penyusun yang sudah menjadi titik konvergensi dan digunakan oleh sebagian besar framework ke standard library
      Promises juga masuk ke standard library setelah dipakai secara luas, dan ini mirip dengan itu