Bab 3: Network Requests

5 menit baca

3.1 Fetch — Dasar

Apa Itu Fetch?

fetch() adalah cara modern JavaScript untuk mengambil data dari server (API). Menggantikan XMLHttpRequest yang ribet.

Analogi: Fetch itu kayak pesan makanan online — kamu kirim pesanan (request), tunggu, lalu terima paket (response). Prosesnya async (kamu bisa ngerjain hal lain sambil nunggu).

Sintaks Dasar

javascript
const response = await fetch(url, options);

GET Request (Ambil Data)

javascript
// Ambil data dari API
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');

// Cek apakah berhasil
if (!response.ok) {
  throw new Error(`HTTP Error: ${response.status}`);
}

// Parse JSON
const user = await response.json();
console.log(user.name); // "Leanne Graham"
console.log(user.email); // "Sincere@april.biz"

Response Properties

javascript
const response = await fetch('https://api.example.com/data');

console.log(response.status);     // 200, 404, 500, dll
console.log(response.ok);         // true kalau status 200-299
console.log(response.statusText); // "OK", "Not Found", dll
console.log(response.headers.get('Content-Type')); // "application/json"
console.log(response.url);        // URL final (setelah redirect)

Membaca Response Body

javascript
const response = await fetch(url);

// Pilih SATU cara baca (body hanya bisa dibaca sekali!)
const json = await response.json();    // parse sebagai JSON
const text = await response.text();    // sebagai string
const blob = await response.blob();    // sebagai Blob (file)
const buffer = await response.arrayBuffer(); // sebagai ArrayBuffer
const form = await response.formData(); // sebagai FormData

POST Request (Kirim Data)

javascript
const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    nama: 'Yazid',
    email: 'yazid@example.com',
    umur: 25
  })
});

const result = await response.json();
console.log(result); // { id: 101, nama: "Yazid", ... }

Headers

javascript
// Buat headers
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');

// Atau langsung di fetch
const response = await fetch(url, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123',
    'X-Custom-Header': 'nilai-custom'
  }
});

// Baca response headers
for (const [key, value] of response.headers) {
  console.log(`${key}: ${value}`);
}
⚠️Jebakan!
  1. fetch TIDAK throw error untuk 404/500! Hanya throw untuk network error
javascript
// ❌ Ini TIDAK akan catch 404
try {
  const res = await fetch('/not-found');
  // res.ok = false, tapi TIDAK throw error!
} catch (e) {
  // Hanya masuk sini kalau NETWORK error (offline, DNS gagal)
}

// ✅ Cek manual
const res = await fetch('/not-found');
if (!res.ok) throw new Error(`Status: ${res.status}`);
  1. Body hanya bisa dibaca SEKALI
javascript
const res = await fetch(url);
const json = await res.json();
const text = await res.text(); // ❌ Error! Body sudah dibaca

3.2 FormData

Apa Itu FormData?

FormData adalah cara mengirim data form (termasuk file!) ke server. Browser otomatis set Content-Type: multipart/form-data.

Analogi: FormData itu kayak amplop besar yang bisa isi macam-macam — teks, foto, dokumen — semuanya dalam satu kiriman.

Membuat FormData

javascript
// Dari form HTML
const form = document.getElementById('myForm');
const formData = new FormData(form); // otomatis ambil semua input

// Manual
const formData = new FormData();
formData.append('nama', 'Yazid');
formData.append('email', 'yazid@example.com');
formData.append('umur', '25');

Upload File dengan FormData

html
<form id="uploadForm">
  <input type="text" name="judul" value="Foto Liburan">
  <input type="file" name="foto" id="fotoInput">
  <button type="submit">Upload</button>
</form>
javascript
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = new FormData(e.target);

  const response = await fetch('/api/upload', {
    method: 'POST',
    body: formData  // JANGAN set Content-Type manual!
  });

  const result = await response.json();
  console.log('Upload berhasil:', result);
});

Metode FormData

javascript
const fd = new FormData();

// Tambah field
fd.append('hobi', 'coding');
fd.append('hobi', 'gaming');  // append = bisa duplikat key

// Set field (replace kalau sudah ada)
fd.set('nama', 'Yazid');
fd.set('nama', 'Budi');  // replace, bukan tambah

// Hapus
fd.delete('hobi');

// Cek ada/tidak
fd.has('nama'); // true

// Ambil nilai
fd.get('nama');    // "Budi" (pertama)
fd.getAll('hobi'); // [] (sudah dihapus)

// Iterasi
for (const [key, value] of fd) {
  console.log(`${key}: ${value}`);
}

Upload File dari Blob

javascript
// Buat file dari canvas
const canvas = document.getElementById('myCanvas');
canvas.toBlob(async (blob) => {
  const formData = new FormData();
  formData.append('gambar', blob, 'screenshot.png'); // param ke-3 = nama file

  await fetch('/api/upload', {
    method: 'POST',
    body: formData
  });
}, 'image/png');
⚠️Jebakan!
  1. JANGAN set Content-Type manual saat pakai FormData — browser perlu set boundary sendiri
javascript
// ❌ SALAH
fetch(url, {
  headers: { 'Content-Type': 'multipart/form-data' }, // JANGAN!
  body: formData
});

// ✅ BENAR - biarkan browser handle
fetch(url, { method: 'POST', body: formData });

3.3 Fetch: Download Progress

Melacak Progress Download

javascript
async function downloadWithProgress(url) {
  const response = await fetch(url);

  // Ambil total ukuran dari header
  const total = +response.headers.get('Content-Length');

  // Baca body sebagai stream
  const reader = response.body.getReader();
  let received = 0;
  const chunks = [];

  while (true) {
    const { done, value } = await reader.read();

    if (done) break;

    chunks.push(value);
    received += value.length;

    const percent = Math.round((received / total) * 100);
    console.log(`Download: ${percent}% (${received}/${total} bytes)`);
  }

  // Gabungkan semua chunks jadi satu
  const allChunks = new Uint8Array(received);
  let position = 0;
  for (const chunk of chunks) {
    allChunks.set(chunk, position);
    position += chunk.length;
  }

  // Konversi ke hasil yang diinginkan
  const text = new TextDecoder().decode(allChunks);
  return JSON.parse(text);
}

// Penggunaan
const data = await downloadWithProgress('https://api.example.com/big-data');

Dengan Progress Bar UI

javascript
async function downloadFile(url, progressBar) {
  const response = await fetch(url);
  const total = +response.headers.get('Content-Length');
  const reader = response.body.getReader();
  let received = 0;
  const chunks = [];

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    chunks.push(value);
    received += value.length;

    // Update progress bar
    progressBar.value = (received / total) * 100;
    progressBar.textContent = `${Math.round((received / total) * 100)}%`;
  }

  const blob = new Blob(chunks);
  return blob;
}
⚠️Jebakan!
  1. Content-Length mungkin tidak ada: Server tidak wajib kirim header ini
  2. Upload progress: fetch TIDAK support upload progress! Pakai XMLHttpRequest untuk itu

3.4 Fetch: Abort

Membatalkan Request

Kadang kamu perlu batalkan request — misal user pindah halaman, atau ketik di search box (batalkan request sebelumnya).

javascript
// Buat controller
const controller = new AbortController();

// Mulai fetch dengan signal
const response = fetch('https://api.example.com/data', {
  signal: controller.signal
});

// Batalkan setelah 5 detik
setTimeout(() => controller.abort(), 5000);

try {
  const data = await response;
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Request dibatalkan!');
  } else {
    throw err; // error lain
  }
}

Contoh: Search dengan Debounce + Abort

javascript
let currentController = null;

async function search(query) {
  // Batalkan request sebelumnya
  if (currentController) {
    currentController.abort();
  }

  // Buat controller baru
  currentController = new AbortController();

  try {
    const response = await fetch(`/api/search?q=${query}`, {
      signal: currentController.signal
    });
    const results = await response.json();
    displayResults(results);
  } catch (err) {
    if (err.name !== 'AbortError') {
      console.error('Search error:', err);
    }
    // AbortError = normal, abaikan saja
  }
}

// Di input handler
searchInput.addEventListener('input', (e) => {
  search(e.target.value);
});

Timeout dengan AbortSignal

javascript
// Modern way (AbortSignal.timeout)
const response = await fetch(url, {
  signal: AbortSignal.timeout(5000) // timeout 5 detik
});

// Manual way (untuk browser lama)
function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  return fetch(url, { signal: controller.signal })
    .finally(() => clearTimeout(timeoutId));
}

3.5 Fetch: Cross-Origin Requests (CORS)

Apa Itu CORS?

CORS (Cross-Origin Resource Sharing) adalah mekanisme keamanan browser. Browser BLOKIR request ke domain lain kecuali server mengizinkan.

Analogi: CORS itu kayak security gedung. Kamu (browser) mau masuk gedung lain (server lain). Security tanya: "Kamu dari mana? Boleh masuk nggak?" Kalau server bilang boleh, baru bisa masuk.

Kenapa Ada CORS?

javascript
// Kamu di https://mysite.com
// Mau ambil data dari https://api.other.com

const res = await fetch('https://api.other.com/data');
// ❌ BLOCKED by CORS policy!
// Browser: "Server api.other.com tidak izinkan akses dari mysite.com"

Cara Kerja CORS

  1. Simple Request (GET, POST dengan Content-Type biasa) → Browser langsung kirim, cek response header
  2. Preflight Request (PUT, DELETE, custom headers) → Browser kirim OPTIONS dulu, baru request asli
javascript
// Simple request - langsung kirim
fetch('https://api.other.com/data'); // GET, no custom headers

// Preflight request - browser kirim OPTIONS dulu
fetch('https://api.other.com/data', {
  method: 'PUT',
  headers: { 'X-Custom': 'value' }
});
// Browser: OPTIONS /data → server jawab "boleh" → baru PUT /data

Response Headers yang Mengizinkan CORS

// Server harus kirim header ini: Access-Control-Allow-Origin: https://mysite.com // atau * untuk semua Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400 // cache preflight 24 jam
javascript
// Default: cookie TIDAK dikirim cross-origin
// Untuk kirim cookie:
const res = await fetch('https://api.other.com/data', {
  credentials: 'include'  // kirim cookie
});

// Server harus respond:
// Access-Control-Allow-Credentials: true
// Access-Control-Allow-Origin: https://mysite.com (TIDAK boleh *)
⚠️Jebakan!
  1. CORS itu aturan BROWSER: Server-to-server tidak kena CORS. Postman juga tidak
  2. Access-Control-Allow-Origin: * + credentials = DITOLAK: Harus spesifik origin
  3. Preflight di-cache: Kalau server ubah CORS policy, mungkin perlu tunggu cache expire

3.6 Fetch API — Opsi Lengkap

Semua Opsi fetch()

javascript
const response = await fetch(url, {
  // Method
  method: 'POST', // GET, POST, PUT, DELETE, PATCH

  // Headers
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },

  // Body (untuk POST/PUT/PATCH)
  body: JSON.stringify(data), // atau FormData, Blob, string

  // Mode
  mode: 'cors',        // cors, no-cors, same-origin

  // Credentials (cookie)
  credentials: 'same-origin', // omit, same-origin, include

  // Cache
  cache: 'default',    // default, no-store, reload, no-cache, force-cache

  // Redirect
  redirect: 'follow',  // follow, error, manual

  // Referrer
  referrer: '',        // URL referrer atau '' untuk tidak kirim

  // Signal (untuk abort)
  signal: controller.signal,

  // Keepalive (untuk beacon/analytics saat page unload)
  keepalive: true
});

Contoh: API Wrapper Lengkap

javascript
class ApiClient {
  constructor(baseUrl, token) {
    this.baseUrl = baseUrl;
    this.token = token;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;

    const config = {
      headers: {
        'Content-Type': 'application/json',
        ...(this.token && { 'Authorization': `Bearer ${this.token}` }),
        ...options.headers
      },
      ...options
    };

    if (config.body && typeof config.body === 'object') {
      config.body = JSON.stringify(config.body);
    }

    const response = await fetch(url, config);

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || `HTTP ${response.status}`);
    }

    return response.json();
  }

  get(endpoint) {
    return this.request(endpoint);
  }

  post(endpoint, data) {
    return this.request(endpoint, { method: 'POST', body: data });
  }

  put(endpoint, data) {
    return this.request(endpoint, { method: 'PUT', body: data });
  }

  delete(endpoint) {
    return this.request(endpoint, { method: 'DELETE' });
  }
}

// Penggunaan
const api = new ApiClient('https://api.example.com', 'myToken123');
const users = await api.get('/users');
const newUser = await api.post('/users', { nama: 'Yazid' });

3.7 URL Objects

Membuat & Memanipulasi URL

javascript
// Buat URL object
const url = new URL('https://site.com:8080/path/page?name=yazid&age=25#section1');

console.log(url.protocol); // "https:"
console.log(url.host);     // "site.com:8080"
console.log(url.hostname); // "site.com"
console.log(url.port);     // "8080"
console.log(url.pathname); // "/path/page"
console.log(url.search);   // "?name=yazid&age=25"
console.log(url.hash);     // "#section1"
console.log(url.origin);   // "https://site.com:8080"

SearchParams — Kelola Query String

javascript
const url = new URL('https://api.example.com/search');

// Tambah parameter
url.searchParams.set('q', 'javascript tutorial');
url.searchParams.set('page', '1');
url.searchParams.set('limit', '10');

console.log(url.toString());
// "https://api.example.com/search?q=javascript+tutorial&page=1&limit=10"

// Otomatis encode karakter spesial!
url.searchParams.set('filter', 'nama=Yazid&umur>20');
// filter=nama%3DYazid%26umur%3E20 (di-encode otomatis)
javascript
// Baca parameter
const url = new URL('https://site.com?a=1&b=2&a=3');

url.searchParams.get('a');    // "1" (pertama)
url.searchParams.getAll('a'); // ["1", "3"] (semua)
url.searchParams.has('b');    // true

// Hapus
url.searchParams.delete('a');

// Iterasi
for (const [key, value] of url.searchParams) {
  console.log(`${key} = ${value}`);
}

// Sort
url.searchParams.sort();

Contoh: Build API URL Dinamis

javascript
function buildApiUrl(base, params) {
  const url = new URL(base);
  for (const [key, value] of Object.entries(params)) {
    if (value !== undefined && value !== null) {
      url.searchParams.set(key, value);
    }
  }
  return url.toString();
}

const apiUrl = buildApiUrl('https://api.example.com/products', {
  category: 'elektronik',
  minPrice: 100000,
  maxPrice: 5000000,
  sort: 'price_asc',
  page: 1
});
// "https://api.example.com/products?category=elektronik&minPrice=100000&..."

3.8 XMLHttpRequest

Kenapa Masih Perlu Tahu?

XMLHttpRequest (XHR) adalah cara lama untuk request HTTP. Masih berguna untuk:

  • Upload progress (fetch belum support)
  • Legacy code yang masih pakai XHR
  • Browser sangat lama yang belum support fetch

Sintaks Dasar

javascript
// GET request
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');

xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  } else {
    console.error(`Error: ${xhr.status}`);
  }
};

xhr.onerror = function() {
  console.error('Network error');
};

xhr.send();

POST dengan XHR

javascript
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/users');
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.onload = function() {
  console.log(JSON.parse(xhr.responseText));
};

xhr.send(JSON.stringify({ nama: 'Yazid', email: 'yazid@mail.com' }));

Upload Progress (Keunggulan XHR)

javascript
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload');

// UPLOAD progress (ini yang fetch belum bisa!)
xhr.upload.onprogress = function(event) {
  if (event.lengthComputable) {
    const percent = Math.round((event.loaded / event.total) * 100);
    console.log(`Upload: ${percent}%`);
    progressBar.value = percent;
  }
};

xhr.upload.onload = () => console.log('Upload selesai!');
xhr.upload.onerror = () => console.error('Upload gagal!');

const formData = new FormData();
formData.append('file', fileInput.files[0]);
xhr.send(formData);
⚠️Jebakan!
  1. Callback hell: XHR pakai event, bukan Promise. Bungkus dengan Promise kalau perlu
  2. Pakai fetch untuk project baru: XHR hanya untuk kasus khusus (upload progress)

3.9 Resumable File Upload

Konsep

Upload file besar bisa gagal di tengah jalan (koneksi putus). Resumable upload = bisa lanjut dari posisi terakhir.

javascript
class ResumableUploader {
  constructor(file, url) {
    this.file = file;
    this.url = url;
    this.chunkSize = 1024 * 1024; // 1MB per chunk
  }

  async getUploadedSize() {
    // Tanya server: sudah terima berapa byte?
    const response = await fetch(this.url, {
      method: 'HEAD',
      headers: { 'X-File-Id': this.file.name + '-' + this.file.size }
    });
    return +response.headers.get('X-Uploaded-Size') || 0;
  }

  async upload(onProgress) {
    const startByte = await this.getUploadedSize();

    if (startByte >= this.file.size) {
      console.log('File sudah terupload sepenuhnya!');
      return;
    }

    console.log(`Melanjutkan dari byte ${startByte}...`);

    // Potong file dari posisi terakhir
    const chunk = this.file.slice(startByte);

    const xhr = new XMLHttpRequest();
    xhr.open('POST', this.url);
    xhr.setRequestHeader('X-File-Id', this.file.name + '-' + this.file.size);
    xhr.setRequestHeader('X-Start-Byte', startByte);
    xhr.setRequestHeader('Content-Type', 'application/octet-stream');

    xhr.upload.onprogress = (e) => {
      const uploaded = startByte + e.loaded;
      const percent = Math.round((uploaded / this.file.size) * 100);
      onProgress?.(percent);
    };

    return new Promise((resolve, reject) => {
      xhr.onload = () => resolve(xhr.response);
      xhr.onerror = () => reject(new Error('Upload failed'));
      xhr.send(chunk);
    });
  }
}

// Penggunaan
const uploader = new ResumableUploader(file, '/api/upload');
await uploader.upload((percent) => {
  console.log(`Progress: ${percent}%`);
});

3.10 Long Polling

Apa Itu Long Polling?

Cara sederhana untuk dapat update real-time dari server. Client kirim request, server TAHAN sampai ada data baru, baru respond.

Analogi: Kayak kamu telpon restoran: "Ada pesanan baru?" Mereka bilang "Tunggu ya..." dan baru jawab kalau memang ada pesanan masuk. Begitu dijawab, kamu langsung telpon lagi.

javascript
async function longPoll(url) {
  while (true) {
    try {
      const response = await fetch(url);

      if (response.status === 502) {
        // Timeout dari server, coba lagi
        continue;
      }

      if (!response.ok) {
        throw new Error(`Status: ${response.status}`);
      }

      const data = await response.json();
      handleNewData(data); // proses data baru

      // Langsung poll lagi
    } catch (err) {
      if (err.name === 'AbortError') break;
      console.error('Poll error:', err);
      // Tunggu sebentar sebelum retry
      await new Promise(r => setTimeout(r, 1000));
    }
  }
}

// Mulai polling
longPoll('/api/messages/subscribe');

Kapan Pakai Long Polling vs WebSocket?

  • Long Polling: Sederhana, works everywhere, cocok untuk update jarang
  • WebSocket: Lebih efisien untuk update sering (chat, game, live data)

3.11 WebSocket

Apa Itu WebSocket?

WebSocket adalah koneksi dua arah yang TETAP TERBUKA antara browser dan server. Beda dengan HTTP yang buka-tutup terus.

Analogi: HTTP itu kayak kirim SMS — satu pesan, satu balasan, selesai. WebSocket itu kayak telepon — sekali tersambung, bisa ngobrol bolak-balik tanpa putus.

Membuat Koneksi

javascript
// Buat koneksi WebSocket
const socket = new WebSocket('wss://server.example.com/chat');
// ws:// = tanpa enkripsi, wss:// = dengan enkripsi (selalu pakai wss!)

// Event: koneksi terbuka
socket.onopen = () => {
  console.log('Terhubung ke server!');
  socket.send('Halo server!');
};

// Event: terima pesan
socket.onmessage = (event) => {
  console.log('Pesan dari server:', event.data);
};

// Event: koneksi tertutup
socket.onclose = (event) => {
  if (event.wasClean) {
    console.log(`Koneksi ditutup bersih. Code: ${event.code}`);
  } else {
    console.log('Koneksi terputus!');
  }
};

// Event: error
socket.onerror = (error) => {
  console.error('WebSocket error:', error);
};

Kirim Data

javascript
// Kirim string
socket.send('Halo!');

// Kirim JSON
socket.send(JSON.stringify({
  type: 'chat',
  message: 'Apa kabar?',
  user: 'Yazid'
}));

// Kirim binary (Blob atau ArrayBuffer)
const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
socket.send(blob);

Contoh: Chat Sederhana

javascript
class ChatClient {
  constructor(url) {
    this.connect(url);
  }

  connect(url) {
    this.socket = new WebSocket(url);

    this.socket.onopen = () => {
      console.log('Chat connected!');
    };

    this.socket.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      this.displayMessage(msg);
    };

    this.socket.onclose = () => {
      console.log('Disconnected. Reconnecting in 3s...');
      setTimeout(() => this.connect(url), 3000);
    };
  }

  send(message) {
    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify({
        type: 'message',
        text: message,
        timestamp: Date.now()
      }));
    }
  }

  displayMessage(msg) {
    const div = document.createElement('div');
    div.textContent = `${msg.user}: ${msg.text}`;
    document.getElementById('chatBox').appendChild(div);
  }
}

const chat = new ChatClient('wss://chat.example.com');

// Kirim pesan
sendButton.onclick = () => {
  chat.send(inputField.value);
  inputField.value = '';
};

Close Codes

javascript
// Tutup koneksi dengan code dan reason
socket.close(1000, 'Selesai'); // 1000 = normal closure

// Common codes:
// 1000 - Normal closure
// 1001 - Going away (halaman ditutup)
// 1006 - Abnormal closure (koneksi putus)
// 1011 - Server error

3.12 Server-Sent Events (SSE)

Apa Itu SSE?

SSE adalah koneksi satu arah: server → client. Lebih sederhana dari WebSocket, cocok untuk notifikasi, live feed, progress update.

Analogi: SSE itu kayak radio — kamu cuma dengar (terima), nggak bisa ngomong balik. Tapi koneksinya tetap nyala terus.

Penggunaan

javascript
// Buat koneksi SSE
const source = new EventSource('/api/notifications');

// Terima pesan (event "message")
source.onmessage = (event) => {
  console.log('Data:', event.data);
  console.log('ID:', event.lastEventId);
};

// Event custom dari server
source.addEventListener('notification', (event) => {
  const data = JSON.parse(event.data);
  showNotification(data.title, data.body);
});

source.addEventListener('progress', (event) => {
  updateProgressBar(+event.data);
});

// Error handling
source.onerror = () => {
  console.log('SSE error, akan reconnect otomatis...');
};

// Tutup koneksi
source.close();

Keunggulan SSE vs WebSocket

FiturSSEWebSocket
ArahServer → ClientDua arah
ProtocolHTTP biasaProtocol khusus (ws://)
ReconnectOtomatis!Manual
KompleksitasSederhanaLebih kompleks
Binary dataTidakYa

Contoh: Live Notification

javascript
function connectNotifications() {
  const source = new EventSource('/api/events');

  source.addEventListener('new-order', (e) => {
    const order = JSON.parse(e.data);
    alert(`Pesanan baru dari ${order.customer}!`);
  });

  source.addEventListener('stock-update', (e) => {
    const item = JSON.parse(e.data);
    updateStockDisplay(item.id, item.quantity);
  });

  source.onerror = () => {
    // EventSource otomatis reconnect!
    console.log('Koneksi terputus, reconnecting...');
  };

  return source;
}

const notifications = connectNotifications();

// Tutup saat halaman ditutup
window.addEventListener('beforeunload', () => {
  notifications.close();
});
⚠️Jebakan!
  1. SSE hanya satu arah: Kalau butuh kirim data ke server, pakai fetch/POST terpisah
  2. Max connections: Browser batasi ~6 koneksi SSE per domain
  3. Tidak support binary: Hanya teks (UTF-8)

🏆 Challenge

Buat "API Explorer" sederhana:

  1. Input URL dan method (GET/POST/PUT/DELETE)
  2. Input untuk headers (key-value pairs)
  3. Input untuk body (JSON)
  4. Tombol "Send" yang mengirim request pakai fetch
  5. Tampilkan: status code, response headers, response body (formatted JSON)
  6. Tombol "Cancel" yang abort request yang sedang berjalan
  7. Tampilkan waktu response (berapa ms)
javascript
// Hint:
// - Pakai AbortController untuk cancel
// - performance.now() untuk hitung waktu
// - JSON.stringify(data, null, 2) untuk format JSON
// - try/catch untuk handle error

Sudah paham materi ini?

Tandai sebagai selesai untuk melacak progress-mu.