DEV Community

Alex Spinov
Alex Spinov

Posted on

Effect Has a Free API — Type-Safe Error Handling for TypeScript

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
Enter fullscreen mode Exit fullscreen mode
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))
);
Enter fullscreen mode Exit fullscreen mode

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'))),
);
Enter fullscreen mode Exit fullscreen mode

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,
);
Enter fullscreen mode Exit fullscreen mode

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'),
);
Enter fullscreen mode Exit fullscreen mode

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 }
);
Enter fullscreen mode Exit fullscreen mode

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)