# Effect-TS: Penanganan Error Fungsional Bertipe di TypeScript

> Bagaimana Effect mengodekan sukses, error bertipe, dan dependensi dalam satu signature — dan menjinakkan async di TypeScript tanpa exception tersembunyi.

URL: https://www.matthewswong.com/id/blog/effect-ts-functional-error-handling/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-07-14
- Updated: 2026-07-14
- Category: Software Development

> **Key takeaway:** Effect (library effect-ts) mengodekan sukses, kegagalan, dan dependensi langsung dalam satu tipe: Effect dari Success, Error, Requirements. Error menjadi nilai yang dilacak compiler alih-alih exception tersembunyi, kerja async tersusun tanpa piramida try/catch, dan dependency injection dicek tipenya di titik pemanggilan. Membutuhkan TypeScript 5.4 ke atas dengan mode strict aktif.

Sistem tipe TypeScript luar biasa sampai ada yang gagal. Fungsi yang bertipe mengembalikan User tetap bisa throw, reject, atau mengembalikan null saat runtime, dan signature-nya tidak mengatakan apa pun soal itu. Anda baru tahu di production. Pada sebuah layanan yang saya kelola, saya menghabiskan satu sore memburu unhandled promise rejection yang diam-diam ditelan tiga lapis kode async, dan stack trace-nya menunjuk baris yang hanya jadi pembawa pesan. Kelas bug seperti itulah yang Effect dirancang untuk membuatnya mustahil diabaikan.

Effect adalah sistem effect fungsional untuk TypeScript. Alih-alih menjalankan side effect seketika, Anda membangun deskripsi workflow yang immutable, lalu mengeksekusinya di tepi program. Muatannya ada pada tipe itu sendiri: ia membawa bukan hanya apa yang dikembalikan sebuah komputasi, tetapi setiap cara ia bisa gagal dan segala yang dibutuhkannya untuk berjalan. Artikel ini membahas tipe Effect, error bertipe, menjinakkan async, dan dependency injection dengan Context dan Layer.

## Tipe Effect: tiga kanal dalam satu signature

Setiap nilai Effect punya tiga parameter tipe, dengan urutan ini: tipe sukses, tipe error, dan tipe requirements. Promise hanya memberi tahu tipe sukses; penolakan bertipe any. Effect membuat ketiganya eksplisit, sehingga signature menjadi kontrak yang ditegakkan compiler. Saat Anda menggabungkan dua effect, union error dan union requirements-nya menyatu otomatis, jadi tidak ada yang hilang diam-diam.

> Baca signature dari kiri ke kanan sebagai kalimat: Effect dari User, UserNotFound atau DbError, Database berarti ini mengembalikan User, bisa gagal dengan UserNotFound atau DbError, dan butuh Database dalam context. Jika kegagalan baru muncul di hilir mana pun, kanal error membesar dan setiap kasus yang belum ditangani muncul sebagai type error saat kompilasi.

## Error bertipe alih-alih exception tersembunyi

Pergeseran intinya: kegagalan adalah nilai biasa yang hidup di kanal error, bukan control-flow yang melompat ke atas stack. Anda memodelkan setiap kegagalan yang diharapkan sebagai kelas tagged error memakai Data.TaggedError, yang menstempel field diskriminan bertipe string ke kelas itu. Diskriminan itulah yang memungkinkan compiler membedakan kasus kegagalan Anda dan memaksa Anda menangani tiap satu.

Untuk memulihkan, catchTag menangani satu kegagalan berdasarkan diskriminannya dan menghapusnya dari kanal error; catchAll menangani semuanya sekaligus. Setelah Anda menangani sebuah tag, compiler mempersempit union error yang tersisa, jadi Anda bisa langsung melihat kegagalan mana yang masih tertunda. Bagian inilah yang berbuah pada kode nyata: tipenya menyusut saat Anda menangani kasus, dan ia tidak akan membiarkan Anda lupa satu pun.

## Menjinakkan async tanpa try/catch

Membungkus Promise adalah tempat kebanyakan orang pertama kali merasakan bedanya. Effect.tryPromise menjalankan fungsi yang mengembalikan promise dan membiarkan Anda memetakan penolakan apa pun menjadi error bertipe pilihan Anda, sehingga penolakan bertipe any dari Promise mentah menjadi nilai bernama di kanal error. Dari situ, timeout, retry dengan backoff, dan batas konkurensi adalah kombinator yang Anda tempelkan ke deskripsi alih-alih pipa buatan tangan yang Anda tulis untuk tiap panggilan.

> Effect.tryPromise mengoper AbortSignal ke fungsi try Anda. Jika Anda membungkus fetch, teruskan signal itu, atau timeout maupun interupsi akan membatalkan effect sementara request di bawahnya tetap berjalan. Melupakan signal mengubah pembatalan Effect menjadi kebohongan dan membocorkan koneksi saat beban tinggi.

## Dependency injection dengan Context dan Layer

Parameter tipe ketiga, requirements, adalah tempat dependency injection tinggal. Anda mendeklarasikan sebuah service sebagai Tag, pengenal unik yang dilacak compiler. Effect apa pun yang memakai service itu mengakumulasi Tag tersebut di kanal requirements-nya, dan ia tidak akan berjalan sampai Anda menyediakan implementasi. Tidak ada container runtime, tidak ada sihir decorator, dan tidak ada crash provider hilang saat startup: dependensi yang tak terpenuhi adalah type error.

Layer adalah resep yang membangun service, mungkin dari service lain, dan bisa mengakuisisi serta melepas resource dengan aman. Menukar DatabaseLive dengan layer DatabaseTest dalam pengujian adalah perubahan satu baris tanpa framework mocking, karena dependensi selalu berupa input eksplisit alih-alih import tersembunyi. Bagi siapa pun yang datang dari NestJS, ini ide yang sama dengan provider dan module, tetapi perkabelannya diverifikasi oleh type-checker alih-alih diselesaikan saat runtime.

## Kapan Effect sepadan, dan kapan tidak

Effect tidak gratis. Kurva belajarnya nyata, pesan error bisa menjadi panjang, dan skrip kecil tidak membutuhkan semua ini. Jujurlah soal trade-off sebelum mengadopsinya ke seluruh tim.

| Perhatian | TypeScript biasa | Effect |
| --- | --- | --- |
| Visibilitas error | Tersembunyi; throw tak bertipe | Bertipe di kanal error |
| Pembatalan async | Perkabelan AbortController manual | Interupsi bawaan |
| Dependency injection | Container runtime atau import | Dicek tipe via requirements |
| Retry dan timeout | Buatan tangan per panggilan | Kombinator Schedule yang komposabel |
| Kurva belajar | Rendah | Curam di awal |

Aturan praktis saya: raih Effect ketika sebuah codebase punya kompleksitas nyata pada mode kegagalan, konkurensi, atau perkabelan dependensi, terutama backend berumur panjang di mana kebenaran saat kegagalan parsial lebih penting daripada jumlah baris kode. Untuk CLI sekali pakai atau endpoint CRUD tipis, async biasa plus tipe Result sudah cukup. Adopsi secara bertahap, di batas satu module lebih dulu, dan biarkan kanal error bertipe membuktikan nilainya sebelum Anda menulis ulang semuanya.

## Sources

- The Effect Type — Effect Documentation
- Expected Errors — Effect Documentation
- Managing Services — Effect Documentation
- Effect-TS/effect — GitHub

## Source links

- https://effect.website/docs/getting-started/the-effect-type/
- https://effect.website/docs/error-management/expected-errors/
- https://effect.website/docs/requirements-management/services/
- https://github.com/Effect-TS/effect

## FAQ

### Apa itu Effect (effect-ts) di TypeScript?

Effect adalah sistem effect fungsional untuk TypeScript, dipublikasikan sebagai paket effect. Ia membiarkan Anda mendeskripsikan sebuah komputasi sebagai nilai immutable yang tipenya membawa tiga hal: apa yang dikembalikan saat sukses, bagaimana ia bisa gagal, dan dependensi apa yang dibutuhkannya. Side effect hanya berjalan saat Anda mengeksekusi deskripsi itu di tepi program.

### Bagaimana Effect menangani error berbeda dari try/catch?

Kegagalan adalah nilai biasa yang hidup di kanal error tipe Effect, bukan exception yang melompat ke atas stack. Anda memodelkan kegagalan yang diharapkan sebagai kelas tagged error dengan Data.TaggedError, lalu menanganinya dengan catchTag atau catchAll. Compiler melacak union error yang tersisa dan memaksa Anda menangani setiap kasus, jadi tidak ada kegagalan yang ditelan diam-diam.

### Apa arti tiga parameter tipe Effect?

Tipe Effect ditulis Effect dari Success, Error, Requirements. Success adalah nilai yang dihasilkan saat berjalan, Error adalah union kegagalan yang diharapkan, dan Requirements adalah kumpulan service yang dibutuhkan effect dari context-nya. Saat Anda menggabungkan effect, union error dan requirements menyatu otomatis.

### Bagaimana dependency injection bekerja di Effect?

Anda mendeklarasikan service sebagai Tag memakai Context.Tag, dan effect apa pun yang memakainya mengakumulasi Tag itu di kanal requirements-nya. Anda menyuplai implementasi dengan Layer via Effect.provide. Dependensi yang tak terpenuhi adalah type error saat kompilasi alih-alih crash runtime, dan menukar layer live dengan layer test adalah perubahan satu baris tanpa framework mocking.

### Haruskah saya memakai Effect untuk setiap proyek TypeScript?

Tidak. Effect punya kurva belajar nyata dan menambah overhead yang tidak dibutuhkan skrip kecil atau endpoint CRUD tipis. Ia berbuah pada backend berumur panjang dengan kompleksitas nyata pada mode kegagalan, konkurensi, atau perkabelan dependensi. Adopsi secara bertahap di batas satu module sebelum menulis ulang apa pun.

---

- HTML version: https://www.matthewswong.com/id/blog/effect-ts-functional-error-handling/
- English: https://www.matthewswong.com/en/blog/effect-ts-functional-error-handling/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
