Bab 5: Document & Resource Loading
Kapan halaman siap? Bagaimana cara memuat script dengan benar? Apa yang terjadi saat gambar gagal dimuat?
5.1 Page Lifecycle: DOMContentLoaded, load, beforeunload, unload
Memuat halaman web itu kayak membangun rumah:
- DOMContentLoaded = Struktur rumah selesai (dinding, atap) — belum ada furniture
- load = Rumah selesai total (furniture, dekorasi, semua terpasang)
- beforeunload = Penghuni mau pindah — "Yakin mau pergi?"
- unload = Penghuni sudah pergi, rumah kosong
DOMContentLoaded
Fire saat HTML sudah selesai di-parse dan DOM tree sudah siap. Gambar dan stylesheet mungkin belum selesai dimuat.
document.addEventListener("DOMContentLoaded", function() {
// DOM sudah siap! Bisa manipulasi elemen
console.log("DOM ready!");
const btn = document.getElementById("my-btn"); // ✅ Pasti ada
btn.addEventListener("click", () => alert("Halo!"));
});Kenapa penting? Karena script di <head> jalan SEBELUM body di-parse. Tanpa DOMContentLoaded, elemen belum ada:
<head>
<script>
// ❌ GAGAL — body belum ada!
document.body.style.background = "red";
// ✅ Tunggu DOM ready
document.addEventListener("DOMContentLoaded", () => {
document.body.style.background = "red";
});
</script>
</head>DOMContentLoaded dan Script
Script biasa MEMBLOKIR DOMContentLoaded:
<script>
// DOMContentLoaded MENUNGGU script ini selesai
// Termasuk kalau script ini fetch data dari server
</script>Kecuali script dengan async atau defer (dibahas di sub-bab berikutnya).
DOMContentLoaded dan Stylesheet
Stylesheet sendiri tidak memblokir DOMContentLoaded. TAPI kalau ada script SETELAH stylesheet, script menunggu stylesheet → DOMContentLoaded menunggu script → efeknya stylesheet memblokir.
<link rel="stylesheet" href="style.css">
<script>
// Script ini menunggu style.css selesai dimuat
// DOMContentLoaded menunggu script ini
// Jadi DOMContentLoaded juga menunggu style.css
</script>window.onload
Fire saat SEMUA resource selesai dimuat (gambar, stylesheet, iframe, dll):
window.addEventListener("load", function() {
// Semua sudah dimuat — gambar sudah tampil, CSS sudah diterapkan
console.log("Halaman selesai total!");
// Bisa baca ukuran gambar yang sebenarnya
const img = document.querySelector("img");
console.log(img.naturalWidth, img.naturalHeight);
});beforeunload — Konfirmasi Sebelum Pergi
window.addEventListener("beforeunload", function(event) {
// Tampilkan dialog konfirmasi (teks tidak bisa di-custom di browser modern)
event.preventDefault();
event.returnValue = ""; // Required untuk beberapa browser
});
// Biasanya dipasang HANYA kalau ada perubahan yang belum disimpan:
let hasUnsavedChanges = false;
input.addEventListener("input", () => { hasUnsavedChanges = true; });
window.addEventListener("beforeunload", function(event) {
if (hasUnsavedChanges) {
event.preventDefault();
event.returnValue = "";
}
});unload — Halaman Ditinggalkan
window.addEventListener("unload", function() {
// Halaman sedang ditutup/navigasi
// Waktu sangat terbatas! Hanya untuk cleanup ringan
// Kirim analytics (pakai navigator.sendBeacon, bukan fetch)
navigator.sendBeacon("/analytics", JSON.stringify({
page: location.href,
timeSpent: Date.now() - startTime
}));
});document.readyState
Cek status loading saat ini:
console.log(document.readyState);
// "loading" — masih memuat HTML
// "interactive" — HTML selesai, resource masih loading (= DOMContentLoaded)
// "complete" — semua selesai (= load)
// Event saat state berubah
document.addEventListener("readystatechange", function() {
console.log("State:", document.readyState);
});
// Pattern: jalankan kode kalau DOM sudah ready ATAU tunggu
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init(); // DOM sudah ready
}// 1. DOMContentLoaded di document, BUKAN window
document.addEventListener("DOMContentLoaded", handler); // ✅
// window.addEventListener("DOMContentLoaded", handler); // Juga bekerja, tapi konvensi di document
// 2. Kalau script di akhir <body>, DOMContentLoaded hampir tidak perlu
// Karena semua elemen sudah ada saat script jalan
// 3. beforeunload: browser modern TIDAK menampilkan custom message
// Hanya dialog generic "Leave site?"
// 4. unload: JANGAN pakai fetch/XMLHttpRequest di sini
// Pakai navigator.sendBeacon() — dijamin terkirim
// 5. DOMContentLoaded TIDAK menunggu gambar
// Kalau perlu ukuran gambar → pakai window.onload atau img.onload🎯 Challenge
// Buat loading screen:
// 1. Tampilkan <div id="loader"> (overlay full screen, "Loading...")
// 2. Saat DOMContentLoaded → log "DOM ready" (loader masih tampil)
// 3. Saat window load → sembunyikan loader dengan animasi fade-out
// 4. Tambahkan beberapa <img> besar untuk melihat perbedaan timing
// 5. Tampilkan waktu antara DOMContentLoaded dan load5.2 Script: async dan defer
Bayangin kamu lagi baca buku (HTML parsing). Ada 3 cara handle "tugas tambahan" (script):
- Biasa (tanpa atribut) = Berhenti baca, kerjakan tugas, lanjut baca
- defer = Catat tugas, lanjut baca, kerjakan semua tugas setelah selesai baca (berurutan)
- async = Suruh orang lain download tugas, lanjut baca, kerjakan tugas begitu siap (tidak berurutan)
Script Biasa (Blocking)
<head>
<script src="big-library.js"></script>
<!-- ❌ Browser BERHENTI parse HTML sampai script selesai download + execute -->
<!-- User melihat halaman kosong lebih lama -->
</head>defer — Download Paralel, Execute Setelah Parse
<head>
<script defer src="library.js"></script>
<script defer src="app.js"></script>
<!-- ✅ Download paralel dengan HTML parsing -->
<!-- Execute SETELAH HTML selesai di-parse, SEBELUM DOMContentLoaded -->
<!-- Urutan DIJAMIN: library.js dulu, baru app.js -->
</head>Karakteristik defer:
- Download paralel (tidak blocking)
- Execute setelah DOM ready
- Urutan terjaga (sesuai urutan di HTML)
- DOMContentLoaded menunggu defer scripts selesai
async — Download Paralel, Execute Segera
<head>
<script async src="analytics.js"></script>
<script async src="ads.js"></script>
<!-- ✅ Download paralel -->
<!-- Execute SEGERA setelah download selesai -->
<!-- ⚠️ Urutan TIDAK dijamin! Yang selesai duluan, jalan duluan -->
</head>Karakteristik async:
- Download paralel (tidak blocking)
- Execute segera setelah download selesai
- Urutan TIDAK dijamin
- DOMContentLoaded TIDAK menunggu async scripts
- Bisa jalan sebelum atau sesudah DOM ready
Perbandingan Visual
HTML: ████████████████████████████████████████
Biasa: ████──download──██execute██──████████
(parsing berhenti)
defer: ████████████████████████████████──execute──
──download──(paralel) (setelah parse)
async: ████████████──execute──█████████████
──download── (segera setelah download)
Kapan Pakai Yang Mana?
| Situasi | Gunakan |
|---|---|
| Script butuh DOM (app utama) | defer ✅ |
| Script independen (analytics, ads) | async |
| Script kecil inline | Taruh di akhir <body> |
| Script bergantung pada script lain | defer (urutan terjaga) |
Dynamic Script
// Membuat script secara dinamis
const script = document.createElement("script");
script.src = "module.js";
// Dynamic script = async by default!
document.body.append(script); // Mulai download, execute segera setelah ready
// Kalau mau urutan terjaga:
script.async = false; // Jadi seperti defer
document.body.append(script);Pattern: Load Script On-Demand
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.append(script);
});
}
// Pakai
async function init() {
await loadScript("https://cdn.example.com/chart.js");
// chart.js sudah siap, bisa pakai
new Chart(...);
}// 1. defer dan async HANYA untuk script EXTERNAL (dengan src)
// <script defer>console.log("ini")</script> → defer DIABAIKAN!
// 2. async script bisa jalan SEBELUM DOM ready
// Jangan akses DOM di async script tanpa cek readyState
// 3. defer script jalan SEBELUM DOMContentLoaded
// Tapi SETELAH HTML selesai di-parse
// 4. Urutan async TIDAK dijamin
// Jangan pakai async kalau script B bergantung pada script A
// 5. type="module" otomatis defer
// <script type="module" src="app.js"></script> → defer by default🎯 Challenge
<!-- Buat halaman dengan 3 script:
1. analytics.js (async) — log "Analytics loaded" + timestamp
2. library.js (defer) — log "Library loaded" + timestamp
3. app.js (defer) — log "App loaded" + timestamp
Pertanyaan:
- Urutan mana yang dijamin?
- Apakah DOMContentLoaded menunggu semua?
- Apa yang terjadi kalau analytics.js sangat besar?
Buat file HTML dan test di browser, perhatikan urutan console.log
-->5.3 Resource Loading: onload dan onerror
Saat browser memuat resource (gambar, script, stylesheet), hasilnya cuma dua: berhasil atau gagal. Kayak pesan delivery — kamu perlu tahu apakah paket sampai atau hilang di jalan.
Script: onload dan onerror
const script = document.createElement("script");
script.src = "https://cdn.example.com/library.js";
script.onload = function() {
// Script berhasil dimuat DAN dieksekusi
console.log("Library siap!");
// Sekarang bisa pakai fungsi dari library
};
script.onerror = function() {
// Gagal memuat (404, network error, CORS block)
console.error("Gagal memuat script!");
};
document.head.append(script);Image: onload dan onerror
const img = document.createElement("img");
img.onload = function() {
console.log(`Gambar dimuat: ${img.naturalWidth}x${img.naturalHeight}`);
document.body.append(img);
};
img.onerror = function() {
console.error("Gambar gagal dimuat!");
// Tampilkan placeholder
img.src = "placeholder.png";
};
img.src = "photo.jpg"; // Mulai loading SETELAH set srcPreload Gambar
// Preload gambar sebelum ditampilkan
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Gagal load: ${src}`));
img.src = src;
});
}
// Pakai
async function showGallery(urls) {
const images = await Promise.all(urls.map(preloadImage));
images.forEach(img => document.body.append(img));
}Stylesheet: onload dan onerror
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "theme-dark.css";
link.onload = function() {
console.log("Stylesheet dimuat!");
};
link.onerror = function() {
console.error("Stylesheet gagal!");
};
document.head.append(link);Cross-Origin (CORS)
Script dari domain lain yang error → browser TIDAK kasih detail error (security):
// Script dari CDN lain
const script = document.createElement("script");
script.src = "https://other-domain.com/script.js";
// Untuk dapat detail error, tambahkan crossorigin
script.crossOrigin = "anonymous";
// Dan server harus kirim header: Access-Control-Allow-OriginGlobal Error Handler: window.onerror
window.onerror = function(message, source, lineno, colno, error) {
console.error("Global error:", {
message, // Pesan error
source, // URL file
lineno, // Baris
colno, // Kolom
error // Error object
});
// Kirim ke error tracking service
sendToErrorTracker({ message, source, lineno, colno });
// return true → cegah error muncul di console
// return false/undefined → error tetap muncul di console
};Contoh: Image Gallery dengan Error Handling
const gallery = document.getElementById("gallery");
const imageUrls = [
"photo1.jpg",
"photo2.jpg",
"broken-link.jpg", // Ini akan gagal
"photo3.jpg"
];
imageUrls.forEach(url => {
const img = document.createElement("img");
img.className = "gallery-img loading";
img.onload = function() {
img.classList.remove("loading");
img.classList.add("loaded");
};
img.onerror = function() {
img.classList.remove("loading");
img.classList.add("error");
img.src = "placeholder.svg"; // Fallback
img.alt = "Gambar tidak tersedia";
};
img.src = url;
gallery.append(img);
});// 1. onload/onerror harus di-set SEBELUM src (untuk img)
// Kalau img sudah di-cache, onload bisa fire synchronously
const img = new Image();
img.onload = handler; // ✅ Set dulu
img.src = "photo.jpg"; // Baru set src
// 2. onerror pada script: TIDAK dapat detail error dari cross-origin
// Hanya "Script error." tanpa info berguna
// Solusi: crossOrigin attribute + CORS header di server
// 3. CSS @import di dalam stylesheet TIDAK trigger onload/onerror terpisah
// 4. iframe juga punya onload (fire saat halaman di dalam iframe selesai)
// 5. Gambar dari cache: onload tetap fire (tapi sangat cepat)🎯 Challenge
// Buat image loader dengan progress:
// 1. Array 5 URL gambar (campur yang valid dan invalid)
// 2. Tampilkan progress: "Loading 2/5..."
// 3. Gambar berhasil → tampilkan di halaman
// 4. Gambar gagal → tampilkan placeholder dengan teks "Error"
// 5. Setelah semua selesai (berhasil atau gagal) → tampilkan summary:
// "Berhasil: 3, Gagal: 2"
// Hint: Promise.allSettled(), img.onload, img.onerrorRingkasan Bab 5
| Sub-bab | Inti |
|---|---|
| Page Lifecycle | DOMContentLoaded (DOM ready) → load (semua ready) → beforeunload → unload |
| async/defer | defer = urutan terjaga, setelah parse. async = segera, tanpa urutan |
| Resource Loading | onload/onerror untuk script, img, link. Promise pattern untuk preload |
Prinsip utama:
- Taruh script di akhir
<body>ATAU pakaidefer asynchanya untuk script independen (analytics, ads)- Selalu handle
onerroruntuk resource external navigator.sendBeacon()untuk kirim data saat unloadDOMContentLoaded= DOM siap,load= semua resource siap
Next: Bab 6 — Miscellaneous (Mutation Observer, Selection & Range, Event Loop)
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.