Assertion vs Type Guard
Kapan pakai as, kapan pakai guard: matriks keputusan lengkap dengan contoh.
Satu prinsip yang mengatur semuanya
as tidak memeriksa apa-apa. Ia hanya membungkam compiler. Type guard (narrowing, type predicate, assertion function) memeriksa saat runtime lalu memberi tahu compiler hasilnya. Setiap kali kamu menulis as, kamu sedang berkata "saya tanggung jawab atas baris ini". Pertanyaan yang selalu harus ditanya: apakah kamu benar-benar bisa tanggung jawab, atau datanya datang dari luar kendalimu?
Matriks keputusan
| Situasi | Pakai | Kenapa |
|---|---|---|
Data dari API / JSON / form / localStorage | Type guard atau Zod | Datanya di luar kendalimu, wajib diverifikasi saat runtime |
| DOM yang kamu tulis sendiri | as setelah null check | Kamu tahu struktur HTML-nya, compiler tidak |
| Union yang bisa dipersempit | Narrowing biasa (typeof / in / discriminant) | Paling aman, nol klaim buta |
| Butuh cek boolean yang reusable | Type predicate (x is T) | Bisa dipakai di if dan .filter() |
| Gagal berarti harus throw | Assertion function (asserts x is T) | Fail fast untuk invariant dan config |
Kalau ragu, ikuti urutan ini: narrowing dulu, guard kalau data dari luar, as hanya sebagai pilihan terakhir.
Contoh SALAH vs BENAR, tiga ronde
Ronde 1: data API.
// SALAH: percaya mentah-mentah pada response
const user = (await res.json()) as User;
tampilkanProfil(user); // kalau API error, crash di dalam
// BENAR: verifikasi dulu, baru pakai
const raw: unknown = await res.json();
if (adalahUser(raw)) {
tampilkanProfil(raw); // raw: User, terbukti saat runtime
} else {
tampilkanError("data user tidak valid");
}Ronde 2: localStorage.
// SALAH: isi localStorage bisa diubah atau dihapus user kapan saja
const settings = JSON.parse(localStorage.getItem("settings")!) as Settings;
terapkanTema(settings.tema); // settings bisa null -> crash
// BENAR: parse sebagai unknown, validasi, sediakan default
const rawSettings: unknown = JSON.parse(localStorage.getItem("settings") ?? "null");
const settings = adalahSettings(rawSettings) ? rawSettings : defaultSettings;
terapkanTema(settings.tema); // selalu amanRonde 3: DOM milik sendiri.
// BOLEH: kamu yang menulis <canvas id="papan"> di HTML
const kanvas = document.getElementById("papan") as HTMLCanvasElement;
const ctx = kanvas.getContext("2d");
// Tapi kalau elemennya opsional, null check dulu baru as:
const el = document.getElementById("opsional");
if (el) {
const kanvas2 = el as HTMLCanvasElement; // aman, sudah pasti ada
}Error compiler yang relevan di perbatasan ini
Dua error yang sering muncul saat orang bimbang antara assertion dan guard:
// 1. Assertion antar tipe yang tidak berhubungan langsung ditolak:
const n = "123" as number;error TS2352: Conversion of type 'string' to type 'number' may be a mistake
because neither type sufficiently overlaps with the other.// 2. Non-null assertion (!) pada elemen yang ternyata tidak ada:
const el2 = document.getElementById("tidak-ada")!;
el2.textContent = "halo";runtime: TypeError: Cannot set properties of null (setting 'textContent')! adalah sepupu as: klaim tanpa verifikasi ("saya yakin ini tidak null"). Compiler percaya, runtime yang menentukan. Error kedua ini bahkan tidak ditangkap compiler sama sekali.
Checklist sebelum menulis as
Tanya tiga hal ini berurutan, berhenti di yang pertama yang jawabannya ya:
- Bisakah pakai narrowing biasa? Kalau ya, pakai itu. Nol risiko, nol klaim.
- Datanya dari luar? (API, input user, storage, URL) Kalau ya, pakai guard atau Zod.
asdilarang di sini. - Informasinya cuma ada di kepalamu? (struktur DOM yang kamu tulis, kontrak internal antar modul yang tidak bisa dilihat compiler) Barulah
asboleh dipakai, idealnya tepat setelah null check.
Catatan teknis: Aktifkan lint rule
@typescript-eslint/no-unnecessary-type-assertiondi project. Rule ini menemukanasyang sebenarnya tidak dibutuhkan, dan jumlahnya biasanya mengejutkan. Banyak assertion ditulis karena malas narrowing, padahal narrowing selalu lebih aman.
Tantangan
Audit assertion
Diberikan 3 potong kode ber-assertion. Tentukan mana yang AMAN (tetap pakai as) dan mana yang BERBAHAYA (ganti jadi guard). Tulis ulang yang berbahaya dengan type predicate sederhana.
// 1. const canvas = document.getElementById("c") as HTMLCanvasElement;
// 2. const user = JSON.parse(localStorage.getItem("u")!) as User;
// 3. function proses(x: string | string[]) {
// const arr = x as string[];
// console.log(arr.length);
// }