
Effect: Chybějící standardní knihovna pro TypeScript
Effect nabízí typově bezpečné zpracování chyb a čitelnější práci s asynchronním kódem.
Effect je TypeScript knihovna, která si klade za cíl být „chybějící standardní knihovnou” pro TypeScript. Přináší funkcionální programování do světa TypeScriptu a pomáhá psát robustnější, bezpečnější a lépe udržovatelný kód.
Vznikla jako odpověď na problémy, které TypeScript sám o sobě neřeší – zejména zpracování chyb, správu zdrojů a práci s asynchronním kódem.
Jinak řečeno se Effect snaží řešit věci, které typicky vadí na JavaScriptu programátorům preferujícím jiné jazyky.
Proč Effect vznikl?
TypeScript má skvělý typový systém, ale některé věci v něm zůstávají problematické:
- Zpracování chyb –
try/catchnezachycuje typy výjimek, takže nevíte, co vám může vyletět - Null/undefined – i s
strictNullChecksje práce s nullable hodnotami nepohodlná - Asynchronní kód – Promise jsou lepší než callbacky, ale stále mají svoje limity
- Správa zdrojů – otevřít soubor, připojit se k databasi a správně to zavřít je překvapivě těžké
- Závislosti – předávání závislostí (dependency injection) v TypeScriptu není standardisované
Effect na všechny tyto problémy nabízí elegantní řešení.
Funkcionální programování ve zkratce
Effect vychází z funkcionálního programování (FP). Pokud s FP nemáte zkušenosti, zde jsou klíčové koncepty:
Čisté funkce
Funkce, která pro stejný vstup vždy vrátí stejný výstup a nemá vedlejší efekty (nemění globální stav, nepíše do database, nevolá API). Čisté funkce jsou snadno testovatelné a předvídatelné.
const add = (a: number, b: number) => a + b
add(2, 3) // vždy 5
Imutabilita
Data se nemění – místo modifikace vytváříte nové kopie. To eliminuje celou kategorii chyb způsobených neočekávanou mutací.
const users = [{ name: "Alice" }]
const newUsers = [...users, { name: "Bob" }]
Komposice funkcí
Malé funkce se skládají do větších celků. Můžete použít vnořené volání nebo pomocnou funkci pipe:
// Vnořené volání – čte se zevnitř ven
const result = save(transform(validate(input)))
// Pomocí pipe – čte se zleva doprava (přirozenější)
const result = pipe(input, validate, transform, save)
pipe není nutná součást FP – je to jen syntaktický nástroj pro lepší čitelnost. Effect i další knihovny ho nabízejí, protože čtení „data protékají funkcemi” je intuitivnější než vnořené závorky.
Algebraické datové typy
Option representuje hodnotu, která může chybět (náhrada za null). Either/Result representuje výpočet, který může selhat. Tyto typy vynucují explicitní ošetření všech případů.
type Option<A> = Some<A> | None
type Result<E, A> = Ok<A> | Err<E>
Effect tyto koncepty integruje do jednotného typu Effect<Success, Error, Requirements>, který kombinuje výsledek, chybu i závislosti.
Hlavní vlastnosti
Typově bezpečné zpracování chyb
V běžném TypeScriptu nevíte, jaké chyby funkce může vyhodit:
// Co může vyhodit? Nevím, podívám se do dokumentace… možná.
function parseJSON(text: string): unknown {
return JSON.parse(text);
}
V Effect je chyba součástí typu:
// Typ říká vše: může vrátit User, nebo selhat s ParseError
const parseUser: Effect<User, ParseError>
Chyby se stávají hodnotami, které můžete předávat, transformovat a zpracovávat stejně jako normální data. Žádné překvapivé výjimky, které vybuchnou za běhu.
Práce s null/undefined
Effect nabízí typ Option jako náhradu za null/undefined. Upřímně – pro většinu případů v TypeScriptu stačí ?. a ??:
const email = user?.profile?.email ?? "default@example.com"
Option se vyplatí hlavně ve dvou situacích:
1. Když používáte Effect – Option se přirozeně integruje s ostatními Effect typy. Můžete ho převést na Effect a naopak:
const program = pipe(
Option.fromNullable(config.apiKey),
Option.match({
onNone: () => Effect.fail(new ConfigError("API key missing")),
onSome: (key) => fetchData(key)
})
)
2. Když potřebujete rozlišit „nenalezeno” od ”chyba" – funkce vracející Option říká „hodnota nemusí existovat a to je OK”. Funkce vracející Effect s chybou říká ”něco se pokazilo":
// Option: "uživatel nemusí existovat, to je normální"
const findUser = (id: string): Option<User>
// Effect: "pokud uživatel neexistuje, je to chyba"
const getUser = (id: string): Effect<User, UserNotFoundError>
Pokud nepoužíváte Effect ekosystém, Option pravděpodobně nepotřebujete.
Bezpečná správa zdrojů
Effect zajišťuje, že zdroje (soubory, připojení k databasi, síťová spojení) jsou vždy správně uvolněny, i když dojde k chybě:
const program = Effect.acquireRelease(
openFile("data.txt"), // Získání zdroje
(file) => closeFile(file) // Uvolnění (vždy se provede)
)
Nemusíte myslet na finally bloky a nemůžete zapomenout zavřít spojení.
Snadná komposice
Effect je navržen tak, aby se jednotlivé části daly snadno skládat dohromady:
const program = pipe(
fetchUser(userId),
Effect.flatMap(validateUser),
Effect.flatMap(saveToDatabase),
Effect.catchTag("NetworkError", handleNetworkError)
)
Každý krok je samostatná jednotka, kterou můžete testovat, znovupoužít a kombinovat s dalšími.
Strukturovaná konkurence
Effect má vestavěnou podporu pro paralelní a konkurentní operace (běh více úloh střídavě) s automatickým zrušením:
// Spustí tři požadavky paralelně a vrátí všechny výsledky
const results = Effect.all([
fetchUserProfile(id),
fetchUserPosts(id),
fetchUserFriends(id)
], { concurrency: "unbounded" })
Pokud některá operace selže nebo je zrušena, Effect automaticky zruší i ostatní běžící operace.
Nahrazuje více knihoven
Effect v sobě obsahuje funkce, které byste jinak řešili pomocí několika různých knihoven:
- Validace a schémata – modul
Schemanahrazuje Zod - Utility funkce – moduly jako
Array,Option,Streamnahrazují část Lodashe - Reaktivní streamy – modul
Streamnabízí podobné možnosti jako RxJS
Místo kombinování různých knihoven s různými API máte jeden konsistentní nástroj.
Základní koncept: Effect jako popis výpočtu
Klíčem k pochopení Effect je uvědomit si, že Effect<Success, Error, Requirements> je popis výpočtu, ne samotný výpočet:
- Success – typ úspěšného výsledku
- Error – typ možné chyby
- Requirements – požadované závislosti (služby, které efekt potřebuje)
// Popis: "Něco, co vrátí User, může selhat s DatabaseError
// a potřebuje DatabaseService"
type GetUser = Effect<User, DatabaseError, DatabaseService>
Teprve když Effect „spustíte”, začne se skutečně vykonávat. Toto oddělení popisu od provedení přináší řadu výhod – můžete efekty skládat, transformovat a testovat, aniž byste cokoli skutečně spouštěli.
Effect.try vs klasický try-catch
Na první pohled může Effect.try vypadat jako zbytečná obálka kolem běžného try-catch. Ve skutečnosti přináší několik zásadních výhod.
Chyba je součástí typu
V klasickém TypeScriptu funkce s try-catch neodhalí, že může selhat:
function parseJSON(input: string): unknown {
try {
return JSON.parse(input)
} catch (e) {
throw new Error(`Parse failed: ${e}`)
}
}
// Typ: (input: string) => unknown
// TypeScript NEVÍ, že funkce může vyhodit Error
V Effect je chyba explicitně součástí typu:
const parseJSON = (input: string) =>
Effect.try({
try: () => JSON.parse(input),
catch: (e) => new Error(`Parse failed: ${e}`)
})
// Typ: (input: string) => Effect<unknown, Error, never>
// Chyba Error je VIDITELNÁ v typu
Kompilátor vás donutí chybu ošetřit – nemůžete ji ignorovat.
Hodnota místo příkazu
try-catch je příkaz (statement), který nemůžete přímo přiřadit nebo skládat. Effect.try vrací hodnotu, kterou můžete rovnou použít v pipeline:
const program = pipe(
parseJSON(input),
Effect.flatMap(validate),
Effect.flatMap(save),
Effect.catchAll(logAndRecover)
)
Odložené vykonání
Effect.try nic nespouští – vytváří pouze popis operace. Skutečné provedení nastane až při zavolání Effect.runPromise:
const parse = Effect.try({ try: () => JSON.parse(input), catch: toError })
// Zatím se nic nestalo
Effect.runPromise(parse)
// Teprve teď se JSON.parse skutečně zavolá
To umožňuje efekt znovupoužít, opakovat při selhání (retry) nebo testovat bez skutečného spuštění.
Plochá struktura místo vnořování
Klasický try-catch vede k vnořování:
try {
const data = JSON.parse(input)
try {
const validated = validate(data)
try {
await save(validated)
} catch { /* handle save error */ }
} catch { /* handle validation error */ }
} catch { /* handle parse error */ }
Effect umožňuje plochou pipeline s ošetřením chyb kdekoli:
const program = pipe(
parseJSON(input),
Effect.flatMap(validate),
Effect.flatMap(save),
Effect.catchTag("ParseError", handleParseError),
Effect.catchTag("ValidationError", handleValidationError),
Effect.catchTag("SaveError", handleSaveError)
)
Jak začít s Effect
Effect můžete do projektu zavádět postupně. Nemusíte přepisovat celou aplikaci:
npm install effect
Začněte s jednou funkcí nebo modulem a postupně rozšiřujte:
import { Effect } from "effect"
// Jednoduchý Effect, který vrátí číslo
const program = Effect.succeed(42)
// Spuštění
Effect.runPromise(program).then(console.log) // 42
Křivka učení
Effect má poměrně strmou křivku učení. Přináší koncepty z funkcionálního programování, které mohou být zpočátku nezvyklé:
- Monády a funktory – i když je nemusíte znát teoreticky, prakticky s nimi pracujete
- Pipe a flow – skládání funkcí místo řetězení metod
- Laziness – efekty se nevykonávají hned, ale až při spuštění
Pro běžné projekty může být Effect zbytečně složitý. Ale pro větší aplikace, kde je důležitá spolehlivost a udržovatelnost, se investice do učení vyplatí.
Kdy Effect použít
Effect se hodí pro:
- Větší aplikace s komplexní business logikou
- Systémy, kde je kritická spolehlivost
- Projekty s mnoha externími závislostmi (database, API, soubory)
- Týmy, které chtějí jednotný přístup ke zpracování chyb
Effect se pravděpodobně nehodí pro:
- Malé projekty a jednoduché skripty
- Týmy bez zkušeností s funkcionálním programováním (alespoň zpočátku)
- Projekty, kde je prioritou rychlost vývoje nad robustností
Frontend vs Backend
Backend – Effect zde září nejvíc. Správa zdrojů (database, soubory), strukturovaná konkurence, retry logika a dependency injection jsou přesně to, co backendové aplikace potřebují.
Frontend – Effect lze použít, ale s rozmyslem:
- Bundle size – Effect není malá knihovna (desítky kB gzip), což může být problém pro performance-kritické weby. Pro optimalisaci rozdělte build do chunků.
- UI reaktivita – pro state management jsou lepší framework-specific nástroje (React Query, Svelte stores, Vue composables)
- Kdy ano – komplexní business logika na klientu, offline-first aplikace, sdílený kód mezi FE a BE
Nejčastější vzor je používat Effect na backendu a na frontendu jen pro sdílenou business logiku nebo validace pomocí modulu Schema.
Ekosystém
Kolem Effect vzniká ekosystém dalších nástrojů:
- Schema – validace a transformace dat (součást hlavního balíčku
effect) - @effect/platform – abstrakce nad platformou (Node.js, Bun, prohlížeč)
- @effect/sql – typově bezpečná práce s SQL databasemi
- @effect/rpc – typově bezpečné RPC volání
Alternativy k Effect
Effect není jediná knihovna, která řeší typově bezpečné zpracování chyb v TypeScriptu. Existuje několik alternativ s různou mírou komplexity:
neverthrow
neverthrow je jednodušší knihovna zaměřená primárně na typově bezpečné chyby pomocí typu Result. Je ideální jako vstupní bod do světa funkcionálního error handlingu.
- Výhody – jednoduchá, malá, snadno se integruje do existujícího kódu
- Nevýhody – pouze error handling, žádná správa zdrojů ani konkurence
- Použití – když chcete typově bezpečné chyby bez velkých změn v projektu
fp-ts
fp-ts je komplexní knihovna pro funkcionální programování v TypeScriptu. Nabízí typy jako Either, Option, Task a další.
- Výhody – rozsáhlá funkcionalita, velká komunita, dobře zdokumentovaná
- Nevýhody – strmá křivka učení, méně intuitivní API než Effect
- Použití – když chcete plnohodnotné FP a nevadí vám složitější syntax
Effect je duchovní nástupce fp-ts. Tvůrce fp-ts, Giulio Canti, se v roce 2023 připojil k Effect týmu. fp-ts zůstává samostatný projekt, ale aktivní vývoj se přesunul na Effect. Effect přináší modernější a přístupnější API, které řeší některé limity fp-ts a nabízí jednotný typ Effect místo více různých efektových typů.
ts-results
ts-results je minimalistická knihovna inspirovaná Rustem, která přináší typy Result a Option.
- Výhody – velmi jednoduchá, blízká Rust syntaxi
- Nevýhody – omezená funkcionalita
- Použití – pro vývojáře zvyklé na Rust
Evolu
Evolu je TypeScript knihovna a local-first platforma. Její „Library” část nabízí podobné FP koncepty jako Effect – Result, Task, runtime validaci typů a dependency injection – ale v jednodušší formě.
- Výhody – jednodušší než Effect, obsahuje i local-first platformu pro offline aplikace s end-to-end šifrováním
- Nevýhody – menší komunita, méně funkcí než plný Effect
- Použití – když chcete FP patterny bez komplexity Effect, nebo budujete local-first aplikaci
Jak vybrat?
| Knihovna | Komplexita | Zaměření |
|---|---|---|
| neverthrow | Nízká | Pouze error handling |
| ts-results | Nízká | Result + Option (Rust styl) |
| fp-ts | Vysoká | Kompletní FP toolkit |
| Effect | Vysoká | Kompletní platforma (FP + runtime) |
| Evolu | Střední | FP základy + local-first platforma |
Pro jednoduché projekty stačí neverthrow. Pro local-first aplikace je zajímavá Evolu. Pro komplexní aplikace, kde potřebujete i správu zdrojů a konkurenci, je Effect lepší volba.
TanStack Query
TanStack Query (dříve React Query) nepoužívá Effect ani fp-ts interně – má minimální závislosti a je navržen jako lightweight knihovna. Lze je však kombinovat: Effect můžete použít v queryFn pro typově bezpečné zpracování chyb a business logiku, zatímco TanStack Query řeší cache a synchronisaci server state.
Effect a Svelte/SvelteKit
Effect lze používat se Svelte a SvelteKit. Protože Effect je čistě TypeScript knihovna, funguje všude tam, kde běží TypeScript.
Integrace se SvelteKit
Pro SvelteKit existuje komunitní template sveltekit-effect-template, který ukazuje, jak Effect integrovat:
- Server-side – Effect můžete používat v
+page.server.tsa+server.tssouborech - Load funkce – pomocné funkce jako
runLoaderspouští Effect a mapují výsledky na SvelteKit odpovědi - API routes – Effect je ideální pro komplexní backend logiku
// +page.server.ts
import { Effect, pipe } from "effect"
export const load = async () => {
const program = pipe(
fetchUserData(),
Effect.map(data => ({ user: data }))
)
return Effect.runPromise(program)
}
Na klientu
Na klientské straně Effect funguje také, ale většina jeho výhod (správa zdrojů, strukturovaná konkurence) se projeví spíš na serveru. Pro reaktivitu v UI je lepší používat Svelte stores a runy.
Závěr
Effect je knihovna, která přináší funkcionální programování do TypeScriptu.
Řeší problémy, které TypeScript sám neřeší – typově bezpečné chyby, správu zdrojů a strukturovanou konkurenci.
Může nahradit několik knihoven najednou (Zod, Lodash, RxJS).
Existují jednodušší alternativy (neverthrow, ts-results) pro základní error handling.
Pro komplexní aplikace to může být dobrá volba.