Error Handling & Data dari Luar: Type Guard dan Validasi
Class error kustom, Result type, mengapa JSON.parse dan respons API tidak otomatis aman, serta menulis type guard untuk memvalidasi data tidak dipercaya sebelum dipakai.
Batas dunia tipe
Ingat bab 1: tipe hanya ada saat kompilasi. Begitu data datang dari luar program — respons API, isi file, input pengguna, localStorage, JSON.parse — TypeScript tidak bisa menjamin bentuknya. Di sinilah banyak bug sungguhan lahir, padahal kode "sudah bertipe".
const data = JSON.parse('{"nama": "Ani"}') // tipe: any 😬
data.umur.toFixed(2) // kompilasi lolos, tapi crash saat jalan
JSON.parse mengembalikan any. Cara aman: terima sebagai unknown, lalu validasi sebelum memakainya.
Type guard untuk memvalidasi
interface Pengguna { nama: string; umur: number }
function adalahPengguna(x: unknown): x is Pengguna {
return (
typeof x === "object" &&
x !== null &&
"nama" in x && typeof x.nama === "string" &&
"umur" in x && typeof x.umur === "number"
)
}
function parsePengguna(json: string): Pengguna | null {
try {
const data: unknown = JSON.parse(json)
return adalahPengguna(data) ? data : null
} catch {
return null // JSON rusak
}
}
parsePengguna('{"nama":"Ani","umur":20}') // { nama: "Ani", umur: 20 }
parsePengguna('{"nama":"Ani"}') // null — umur hilang
parsePengguna('bukan json') // null
"nama" in x setelah typeof x === "object" && x !== null mempersempit x sehingga x.nama boleh dibaca. Rantai pemeriksaan ini adalah resep standar validasi manual.
as bukan validasi
const p = JSON.parse(teks) as Pengguna // ⚠️ menutup mulut compiler, TIDAK memeriksa apa pun
const q = <Pengguna>JSON.parse(teks) // sama saja
Pakai as hanya bila kamu benar-benar tahu lebih banyak daripada compiler (dan lebih baik memverifikasinya). Untuk data eksternal, gunakan guard di atas atau pustaka validasi.
Pustaka validasi schema
Untuk data kompleks, menulis guard manual melelahkan. Pustaka seperti Zod, Valibot, atau ArkType mendefinisikan skema sekali lalu menurunkan tipe sekaligus memvalidasi saat runtime:
import { z } from "zod"
const SkemaPengguna = z.object({ nama: z.string(), umur: z.number().int().min(0) })
type Pengguna = z.infer<typeof SkemaPengguna> // tipe diturunkan dari skema
const hasil = SkemaPengguna.safeParse(dataDariApi)
if (hasil.success) console.log(hasil.data.nama)
else console.log(hasil.error.issues)
Class error kustom
class ErrorValidasi extends Error {
constructor(public readonly kolom: string, pesan: string) {
super(pesan)
this.name = "ErrorValidasi"
}
}
class ErrorTidakDitemukan extends Error {
constructor(public readonly id: number) {
super(`Data ${id} tidak ditemukan`)
this.name = "ErrorTidakDitemukan"
}
}
try {
throw new ErrorValidasi("email", "Format email salah")
} catch (e) {
if (e instanceof ErrorValidasi) console.log(e.kolom, e.message) // e: ErrorValidasi
else if (e instanceof ErrorTidakDitemukan) console.log(e.id)
else throw e // yang tak dikenal: lempar lagi
}
Result type: error sebagai nilai
Alternatif exception: fungsi mengembalikan hasil yang mencantumkan kemungkinan gagal di tipenya. Compiler memaksa pemanggil menanganinya (ingat discriminated union bab 8):
type Hasil<T, E = string> =
| { ok: true; nilai: T }
| { ok: false; galat: E }
function bagi(a: number, b: number): Hasil<number> {
return b === 0 ? { ok: false, galat: "Dibagi nol" } : { ok: true, nilai: a / b }
}
const h = bagi(8, 2)
if (h.ok) console.log(h.nilai) // 4
else console.log(h.galat)
| Pendekatan | Cocok untuk |
|---|---|
throw + class error | Kesalahan tak terduga / kondisi luar biasa |
| Result type | Kegagalan yang wajar dan diharapkan (validasi, pencarian) |
Mengembalikan null | Kasus sederhana "ada/tidak ada" |
Operator satisfies
satisfies memeriksa bahwa nilai memenuhi sebuah tipe tanpa mengubah tipe aslinya yang lebih spesifik:
const rute = {
beranda: "/",
profil: "/profil",
} satisfies Record<string, string>
rute.beranda // tetap dikenali sebagai kunci yang ada; typo seperti rute.berandaa → error
Kesalahan umum pemula
- Memakai
as Tipepada respons API dan menganggapnya aman. - Menelan error dengan
catch {}kosong sehingga masalah tak terlihat. - Melempar string (
throw "gagal") alih-alihError— kehilangan stack trace.