Moderní tvorba webových aplikací
O webu

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.

18 minut

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í chybtry/catch nezachycuje typy výjimek, takže nevíte, co vám může vyletět
  • Null/undefined – i s strictNullChecks je 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čí ?.??:

const email = user?.profile?.email ?? "default@example.com"

Option se vyplatí hlavně ve dvou situacích:

1. Když používáte EffectOption 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 Schema nahrazuje Zod
  • Utility funkce – moduly jako Array, Option, Stream nahrazují část Lodashe
  • Reaktivní streamy – modul Stream nabí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 ResultOption.

  • 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 SvelteSvelteKit. 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.ts+server.ts souborech
  • Load funkce – pomocné funkce jako runLoader spouš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ů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.

Co si myslíte o tomto článku?

Diskuse

Novinky e-mailem

Když budu mít něco opravdu zajímavého, můžu vám to poslat e-mailem

Přidej se k 500+ čtenářům
Jen kvalitní obsah
Žádný spam

Web jecas.cz píše Bohumil Jahoda, kontakt
Seznam všech článků · Témata · Zkratky
2013–2026