Effect is a TypeScript library for building robust, type-safe applications with structured concurrency, typed errors, and dependency injection. Think of it as Rust's Result type meets Go's goroutines — in TypeScript.
Why Effect?
- Typed errors — know exactly what can fail at compile time
- Dependency injection — built-in, type-safe service layer
- Structured concurrency — fibers, interruption, resource management
- Retry, timeout, caching — all composable and type-safe
Quick Start
npm install effect
import { Effect } from 'effect';
// An effect that can fail with string or succeed with number
const divide = (a: number, b: number): Effect.Effect<number, string> =>
b === 0
? Effect.fail('Division by zero')
: Effect.succeed(a / b);
// Run it
const result = Effect.runSync(divide(10, 2)); // 5
// Handle errors
const program = divide(10, 0).pipe(
Effect.catchAll((error) => Effect.succeed(-1))
);
Typed Error Channel
class NetworkError {
readonly _tag = 'NetworkError';
constructor(readonly message: string) {}
}
class ParseError {
readonly _tag = 'ParseError';
constructor(readonly input: string) {}
}
const fetchUser = (id: number): Effect.Effect<User, NetworkError | ParseError> =>
Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: () => fetch(`/api/users/${id}`),
catch: (e) => new NetworkError(String(e)),
});
const data = yield* Effect.tryPromise({
try: () => response.json(),
catch: (e) => new ParseError(String(e)),
});
return data as User;
});
// TypeScript knows exactly what errors are possible
const handled = fetchUser(1).pipe(
Effect.catchTag('NetworkError', (e) => Effect.succeed(defaultUser)),
Effect.catchTag('ParseError', (e) => Effect.fail(new Error('Bad data'))),
);
Services (Dependency Injection)
import { Context, Layer } from 'effect';
// Define service interface
class Database extends Context.Tag('Database')<
Database,
{ query: (sql: string) => Effect.Effect<any[]> }
>() {}
// Use in program
const getUsers = Effect.gen(function* () {
const db = yield* Database;
return yield* db.query('SELECT * FROM users');
});
// Provide implementation
const PostgresLayer = Layer.succeed(Database, {
query: (sql) => Effect.succeed([{ id: 1, name: 'Alice' }]),
});
// Run with real DB
const result = getUsers.pipe(
Effect.provide(PostgresLayer),
Effect.runPromise,
);
Retry and Timeout
import { Schedule } from 'effect';
const robust = fetchUser(1).pipe(
// Retry 3 times with exponential backoff
Effect.retry(Schedule.exponential('100 millis').pipe(
Schedule.compose(Schedule.recurs(3))
)),
// Timeout after 5 seconds
Effect.timeout('5 seconds'),
);
Concurrency
// Run in parallel
const [user, posts, settings] = yield* Effect.all([
fetchUser(1),
fetchPosts(1),
fetchSettings(1),
], { concurrency: 'unbounded' });
// Process stream with concurrency limit
const results = yield* Effect.forEach(
userIds,
(id) => fetchUser(id),
{ concurrency: 5 }
);
Building reliable data pipelines? Check out my Apify actors for production-grade web scraping, or email spinov001@gmail.com for custom TypeScript solutions.
Effect, fp-ts, or plain try/catch — how do you handle errors? Share below!
Top comments (0)