Ada empat cara membuat papan Bu Rini tetap segar: short polling, long polling, SSE, dan WebSocket. Bedanya ada di siapa yang bicara, berapa koneksi yang dipakai, dan apa yang terjadi saat jaringan putus. Bayangin empat cara Bu Rini dapat kabar dari Mas Eko. Short polling: Bu Rini jalan ke belakang tiap 30 detik dan bertanya. Long polling: Bu Rini bertanya, lalu Mas Eko menahan jawabannya sampai ada yang berubah (atau 30 detik lewat), dan Bu Rini langsung bertanya lagi. SSE: Mas Eko punya pengeras suara ke depan, dia bisa bicara, Bu Rini cuma mendengar. WebSocket: telepon yang terus tersambung, dua-duanya bisa bicara kapan saja.
🧪 Tebakan cepat: Bu Rini membuka papan di 7 tab Chrome. Tiap tab memakai SSE ke server yang masih HTTP/1.1. Apa yang terjadi di tab ketujuh: tetap update, muncul error, atau macet menunggu? Jawabannya ada di bagian "Batas 6 koneksi per domain".
Short polling
setInterval(async () => {
const res = await fetch('/papan') // tanya tiap 30 detik
document.querySelector('#papan').innerHTML = await res.text()
}, 30000)
Kelemahan utamanya: boros request. Browser terus bertanya walaupun gak ada data baru. 120 request per jam per tab, dan kalau dalam sejam cuma ada 6 perubahan, 114 request pulang membawa papan yang sama. Tiap request juga membawa header lengkap: di pelajaran sebelumnya satu pertanyaan diukur 1.023 byte, padahal kabarnya cuma satu kata. Jedanya bisa dari hampir 0 sampai selebar interval, rata-rata setengahnya (15 detik).
Long polling
async function tungguKabar() {
for (;;) {
try {
const res = await fetch('/papan/tunggu') // server menahan jawaban sampai 30 detik
if (res.status === 200) tampilkan(await res.text())
// 204: 30 detik lewat tanpa kabar, langsung tanya lagi
} catch {
await new Promise((r) => setTimeout(r, 3000)) // jaringan putus: jeda dulu
}
}
}
tungguKabar()
- Server gak langsung menjawab. Dia menahan request-nya sampai ada perubahan, lalu menjawab saat itu juga. Kalau 30 detik lewat tanpa kabar, server menjawab
204 No Content. - Jedanya tinggal satu perjalanan jaringan, karena jawaban dikirim begitu perubahan terjadi.
- Tapi tiap kabar tetap satu request HTTP lengkap, dan saat sepi tetap ada satu request tiap 30 detik.
catchdengan jeda penting: tanpa itu, saat server mati loop-nya mengirim request terus tanpa henti.
Server-Sent Events (SSE)
const sumber = new EventSource('http://localhost:3000/papan/stream')
sumber.addEventListener('status', (event) => {
const { id, status } = JSON.parse(event.data)
document.querySelector(`#status-${id}`).textContent = status
})
Satu response HTTP yang gak pernah selesai: server terus menulis kabar ke dalamnya. Arahnya cuma dari server ke browser. Kalau putus, browser menyambung lagi sendiri (diukur: Chromium 147 dan Safari menunggu 3 detik, Firefox 146 menunggu 5 detik). Isinya teks. Detailnya di pelajaran Server-Sent Events.
WebSocket
const ws = new WebSocket('ws://localhost:3000/papan')
ws.onmessage = (event) => {
const { id, status } = JSON.parse(event.data)
document.querySelector(`#status-${id}`).textContent = status
}
tombolSetrika.onclick = () => ws.send(JSON.stringify({ type: 'status', id: 12, status: 'Disetrika' }))
Satu koneksi, dua arah: tablet Mas Eko bisa mengirim lewat koneksi yang sama dengan yang menerima. Tiap pesan dibungkus frame dengan header 2 sampai 14 byte, bukan header HTTP. Kalau putus, browser gak menyambung lagi sendiri: kamu yang menulis kodenya. Servernya harus bisa bicara protokol WebSocket. Detailnya di pelajaran WebSocket Fundamentals.
Dibandingkan dengan angka
Satu tab, satu jam, 6 perubahan status. Ukuran per request, per koneksi, dan per kabar diukur di Chromium 147 terhadap server lokal tanpa cookie (papan di port 8000, server realtime di port 3000). Lab di halaman ini menghitung ulang tabel ini untuk jumlah perubahan dan tab pilihanmu.
| Short polling 30 dtk | Long polling, tahan 30 dtk | SSE | WebSocket | |
|---|---|---|---|---|
| Arah | browser bertanya | browser bertanya, server menahan | server ke browser | dua arah |
| Request per jam | 120 | 120 | 1 | 1 |
| Byte per jam | 122.760 | 90.042 | 1.209 | 904 |
| Rata-rata tunggu | 15 dtk | 1 perjalanan jaringan | 1 perjalanan jaringan | 1 perjalanan jaringan |
| Kalau putus | request berikutnya jalan lagi | loop-mu yang mengulang | otomatis | tulis sendiri |
| Slot koneksi HTTP/1.1 | dipakai sebentar | 1 slot terus | 1 slot terus | gak memakai slot |
- Long polling: 114 jawaban
204kosong (111 byte header), 6 jawaban berisi papan, plus 120 request 625 byte. - SSE: 578 byte request dan 241 byte header jawaban sekali saja, lalu 65 byte per kabar.
- WebSocket: 487 byte handshake dan 129 byte jawaban sekali saja, lalu 48 byte per kabar.
Batas 6 koneksi per domain
Di HTTP/1.1, browser cuma membuka 6 koneksi sekaligus ke satu domain, dan batas itu dibagi semua tab. SSE dan long polling memegang satu koneksi terus-menerus. Diuji di Chromium 147, Firefox 146, dan WebKit 26.4 (mesin Safari): dengan 6 stream SSE terbuka, request ketujuh ke domain yang sama masih menunggu setelah 2 detik. Jadi tab ketujuh macet menunggu, tanpa error, dan request biasa ke domain itu ikut antre. Itu jawaban tebakannya.
Di HTTP/2, semua request ke satu domain berbagi satu koneksi sebagai stream-stream terpisah. Batasnya dari server, biasanya 100 stream (nilai minimal yang disarankan RFC 9113). Jadi SSE di HTTP/2 aman untuk banyak tab. WebSocket gak memakai slot itu: di ketiga browser, 10 WebSocket terbuka sekaligus dan request biasa tetap lewat.
Proxy juga perlu diperhatikan. nginx secara bawaan menutup koneksi yang diam 60 detik (proxy_read_timeout) dan menampung response sebelum diteruskan, jadi SSE butuh header X-Accel-Buffering: no dan kabar kecil berkala supaya gak dianggap diam. WebSocket butuh proxy yang meneruskan header Upgrade dan Connection.
Satu lagi: WebTransport
WebTransport berjalan di atas HTTP/3 (QUIC). Dia membuka banyak stream sekaligus dan bisa mengirim datagram yang boleh hilang, cocok untuk game. Menurut data MDN browser-compat-data 8.1.5, dukungannya: Chrome dan Edge sejak versi 97, Firefox sejak 114, Safari sejak 26.4. Server dan library-nya masih sedikit, jadi jalur ini tetap memakai WebSocket.
Cara memilih
- Data cuma mengalir dari server ke browser (papan, notifikasi, harga live): SSE. Paling sederhana untuk push satu arah, dan reconnect-nya sudah ada.
- Dua arah dan sering (chat, kursor bersama, game): WebSocket.
- Jarang berubah, boleh telat setengah interval: short polling.
- Push dibutuhkan tapi gak bisa memasang server realtime (hosting yang cuma melayani request biasa): long polling, dengan waktu tahan di bawah timeout proxy.
⚠️ Jebakan yang sering ketemu
- SSE di banyak tab lewat HTTP/1.1. Tab ketujuh macet tanpa error. Pakai HTTP/2, atau satu koneksi yang dibagi antar tab.
- Long polling tanpa jeda saat error. Server mati, loop-nya menembakkan request ribuan kali per detik.
- Mengira WebSocket menyambung lagi sendiri. Yang otomatis cuma SSE. WebSocket harus kamu sambung ulang di event
close. - Proxy yang menampung atau memutus. SSE yang tertahan nginx datang bergerombol, dan koneksi diam 60 detik diputus. Matikan buffering dan kirim kabar kecil berkala.
TL;DR: Short polling bertanya tiap N detik: kelemahan utamanya boros request (120 per jam per tab, 122.760 byte, padahal cuma 6 yang membawa kabar) dan rata-rata telat setengah interval. Long polling menahan request sampai ada kabar: jedanya kecil, tapi tiap kabar tetap satu request HTTP lengkap (90.042 byte per jam). SSE adalah satu response yang terus mengalir dari server ke browser, dengan reconnect otomatis: pilih SSE kalau data cuma mengalir satu arah dari server ke client (1.209 byte per jam). WebSocket adalah satu koneksi dua arah dengan frame kecil, tanpa reconnect otomatis (904 byte per jam). Di HTTP/1.1 browser cuma membuka 6 koneksi per domain untuk semua tab, jadi SSE ketujuh macet. HTTP/2 menghapus batas itu. WebTransport (HTTP/3) sudah ada di Chrome 97, Firefox 114, dan Safari 26.4, tapi masih muda.