Seluruh pernyataan import yang kita pakai sejauh ini bersifat statis: tulisannya harus berada di tingkat atas berkas, jalurnya berupa teks tetap, dan Node menyelesaikan seluruhnya sebelum badan program berjalan. Ada situasi ketika keputusan itu baru bisa diambil saat program berjalan — misalnya memuat module format laporan hanya ketika pengguna benar-benar membuka laporan, atau memilih module sesuai pengaturan yang tersimpan.

Untuk kebutuhan itu JavaScript menyediakan dynamic import: bentuk pemanggilan import(...) yang boleh ditulis di mana saja, menerima jalur berupa ekspresi apa pun, dan mengembalikan Promise. Chapter ini membedah bentuk tersebut, pemakaiannya bersyarat, serta penanganan kegagalannya. Pemisahan ini juga membuka jalan bagi pola yang lebih besar — pemuatan per halaman pada aplikasi web — meski di level course ini kita cukup berlatih dengan dua module format kecil.

import() Mengembalikan Promise #

Struktur berkas chapter ini: dua module pendukung dengan API kembar, ditambah utama.mjs yang memakai import() dalam tiga peran berbeda — objek Promise mentah, await untuk menunggunya, dan ekspresi jalur dinamis.

Perbedaan paling mendasar: import statis adalah pernyataan (statement), sedangkan import() adalah pemanggilan yang menghasilkan nilai. Nilai itu berupa Promise — objek yang mewakili hasil yang akan tersedia kemudian. Kita buktikan langsung pada awal berkas utama.mjs:

js utama.mjs
// utama.mjs — memilih module yang dimuat saat program berjalan
const janji = import('./format-rupiah.mjs');
console.log(janji instanceof Promise);

const { format: formatRupiah } = await janji;
console.log(formatRupiah(25000));

const mode = "tanggal";
const modul = await import("./format-" + mode + ".mjs");
console.log(modul.format("2026-03-15"));

try {
  await import('./modul-tidak-ada.mjs');
} catch (error) {
  console.log("Gagal memuat: " + error.code);
}

Berkas pendukungnya sengaja dibuat kembar bentuknya: keduanya menawarkan fungsi bernama format dengan perilaku berbeda. Berkas format-rupiah.mjs:

// format-rupiah.mjs — format angka menjadi teks rupiah
export function format(nilai) {
  return "Rp" + nilai.toLocaleString("id-ID");
}

Dan berkas format-tanggal.mjs:

// format-tanggal.mjs — format teks menjadi label tagihan
export function format(nilai) {
  return "Tagihan per " + nilai;
}

Menjalankan node utama.mjs menghasilkan:

Output yang Dihasilkan:

text
true
Rp25.000
Tagihan per 2026-03-15
Gagal memuat: ERR_MODULE_NOT_FOUND

Mari kita telaah baris-baris pentingnya:

  1. Baris 2–3 (const janji = import('./format-rupiah.mjs'); ...): hasil pemanggilan import() disimpan tanpa await lebih dulu. Pemeriksaan instanceof Promise menjawab true — bukti bahwa import() menghasilkan Promise, bukan nilai module secara langsung.
  2. Baris 5–6 (const { format: formatRupiah } = await janji;): kata await menunggu Promise terpenuhi, lalu hasilnya — objek namespace module — di-destructuring untuk mengambil fungsi format dengan nama lokal formatRupiah. Pemanggilannya menghasilkan Rp25.000.
  3. Baris 8–10 (const modul = await import("./format-" + mode + ".mjs");): jalur di dalam import() boleh berupa ekspresi apa pun — di sini penyambungan teks yang membentuk "./format-tanggal.mjs". Inilah yang mustahil dilakukan impor statis, yang jalurnya harus teks tetap.
  4. Baris 12–16 (try { await import('./modul-tidak-ada.mjs'); } ...): kegagalan memuat berjalan lewat jalur penolakan Promise. Dengan try–catch di sekeliling await, penolakan itu tertangkap sebagai error, dan error.code menampilkan kode galat Node: ERR_MODULE_NOT_FOUND.

Promise yang dikembalikan import() mengikuti dua jalur yang sama dengan Promise pada umumnya: terpenuhi (fulfilled) dengan objek namespace module sebagai nilainya, atau ditolak (rejected) bila module gagal dimuat — jalur terakhir inilah yang ditangani try–catch di bagian akhir berkas. Karena import() menghasilkan Promise biasa, hasilnya juga dapat diproses tanpa await, misalnya lewat method .then(); cara itu dibahas tuntas di section Asynchronous JavaScript.

Memuat Module Bersyarat #

Gabungan import() dengan ekspresi jalur membuka pola pemilihan module saat berjalan. Baris 8–10 di atas adalah versi sederhananya: variabel mode menentukan module mana yang dimuat, dan mengganti nilainya menjadi "rupiah" membuat program memuat format-rupiah.mjs sebagai gantinya. Pola yang sama berlaku untuk keputusan yang baru diketahui saat berjalan — pilihan pengguna, konfigurasi, atau kondisi jaringan.

Karena import() adalah pemanggilan biasa, ia juga boleh ditulis di dalam fungsi, percabangan, maupun penanganan peristiwa — bukan hanya di tingkat atas berkas. Bentuk percabangan yang paling sering dipakai:

js Percabangan memilih module
const mode = "rupiah";

if (mode === "rupiah") {
  const modul = await import('./format-rupiah.mjs');
  console.log(modul.format(25000));
} else {
  const modul = await import('./format-tanggal.mjs');
  console.log(modul.format("2026-03-15"));
}

Output yang Dihasilkan:

text
Rp25.000

Dengan nilai mode seperti di atas, hanya format-rupiah.mjs yang dimuat dan keluarannya Rp25.000; mengganti nilainya membuat format-tanggal.mjs yang dimuat dan keluarannya berubah menjadi Tagihan per 2026-03-15. Catatan penting: module yang tidak terpilih memang tidak dimuat sama sekali — bukan dimuat lalu dibuang — sehingga pola ini benar-benar menghemat pekerjaan, bukan sekadar menyembunyikannya.

Sebagai pegangan, tiga situasi yang paling sering membenarkan import():

  • Fitur yang jarang dipakai. Ekspor laporan khusus atau alat pengeditan lanjutan mungkin hanya dibuka sebagian kecil pengguna; biaya memuatnya sebaiknya menunggu hingga benar-benar dibutuhkan.
  • Pilihan yang baru diketahui saat berjalan. Module yang bergantung pada pengaturan, peran pengguna, atau kemampuan perangkat tidak mungkin dinyatakan sebagai impor statis.
  • Memecah berkas yang besar. Saat module utama makin gemuk, bagian yang tidak dibutuhkan di jalur awal dapat dipindah ke module terpisah dan dimuat dinamis.

Menangani Kegagalan Memuat #

Kegagalan memuat adalah jalur normal yang layak disiapkan, karena sumber kegagalannya berada di luar kendali kode: berkas tidak ada, jalur salah, atau jaringan putus pada kasus browser. Dua poin praktis:

  • Di Node, kegagalan karena berkas tidak ditemukan memakai kode ERR_MODULE_NOT_FOUND — terlihat pada keluaran Gagal memuat: ERR_MODULE_NOT_FOUND di atas. Kode ini stabil dan aman diperiksa; pesan lengkapnya jauh lebih panjang dan berisi jalur absolut, sehingga tidak layak dibandingkan sebagai teks.
  • Di browser, kegagalan pengambilan berkas module memunculkan TypeError dengan pesan seputar kegagalan pengambilan sumber (fetch). Kode galatnya tidak sama dengan Node, sehingga penanganan lintas lingkungan sebaiknya memeriksa keberadaan properti secara hati-hati, bukan mengandalkan satu kode tertentu.

Pola penanganannya sama dengan penanganan galat pada umumnya: tangkap, catat, lalu putuskan tindak lanjutnya — mencoba ulang, memuat module alternatif, atau menampilkan pesan yang bermakna kepada pengguna. Yang perlu dihindari hanyalah membiarkan penolakan Promise tidak tertangani, karena program akan berhenti tanpa penjelasan yang terbaca.

Dengan dynamic import, pemilihan module kini berada di tangan program yang berjalan. Chapter berikutnya beralih ke pertanyaan yang lebih mendasar: di mana variabel-variabel dalam sebuah module sebenarnya hidup, dan seberapa jauh mereka terlihat dari luar.