← 返回首页

Effect-TS:TypeScript 缺失的标准库

2026/3/22

TypeScript 函数式编程 Effect-TS

Effect-TS:TypeScript 缺失的标准库

TypeScript/JavaScript 是最受欢迎的编程语言,但仍然缺少标准库。Effect 正在填补这个空白。


为什么需要 Effect?

2022 State of JavaScript 调查

最想要的 JS 特性

排名特性Effect 支持
2Standard Library✅ 核心
4Immutable Data Structures✅ Effect/Data
5Observable✅ Effect/Stream
6Pipe Operator✅ Effect/Function
8Pattern Matching✅ Effect/Match

Effect 提供什么

核心能力

模块说明
Effect类似 Promise 但更强大:类型安全错误、取消、资源管理
Context依赖注入,无需手动传递参数
Layer服务组合和生命周期管理
Stream异步流处理
Schedule重试、节流、调度
Match模式匹配
Schema数据验证和序列化

一行代码展示

// 普通 Promise:错误是 any
declare function fetchUser(id: number): Promise<User>

// Effect:错误是类型安全的
declare function fetchUser(id: number): Effect.Effect<User, NetworkError | NotFoundError>

核心概念速览

Effect 类型

Effect<Success, Error, Requirements>
  • Success:成功时返回的类型
  • Error:失败时返回的类型(类型安全!)
  • Requirements:需要的服务(依赖注入)

基础示例

import { Effect } from "effect"

// 成功
const success = Effect.succeed(42)

// 失败
const failure = Effect.fail("something went wrong")

// 从同步代码
const fromSync = Effect.sync(() => Math.random())

// 从异步代码
const fromPromise = Effect.tryPromise(() => fetch("/api"))

组合 Effect

import { Effect } from "effect"

const program = Effect.gen(function* (_) {
  const a = yield* _(Effect.succeed(1))
  const b = yield* _(Effect.succeed(2))
  return a + b
})

// 运行
Effect.runPromise(program) // Promise(3)

错误处理

import { Effect } from "effect"

const program = Effect.fail("error").pipe(
  Effect.catchAll((e) => Effect.succeed(`recovered from ${e}`))
)

Effect.runPromise(program) // "recovered from error"

重试

import { Effect, Schedule } from "effect"

const program = Effect.tryPromise(() => fetch("/api")).pipe(
  Effect.retry(Schedule.exponential("100 millis"))
)

超时

import { Effect } from "effect"

const program = slowOperation.pipe(
  Effect.timeout("5 seconds")
)

与 Promise 对比

特性PromiseEffect
错误类型any类型安全
取消
重试手动内置
超时手动内置
资源管理try/finallyScope
依赖注入手动Context
并发控制手动内置

Context:依赖注入

定义服务

import { Context, Effect } from "effect"

class Logger extends Context.Tag("Logger")<
  Logger,
  { log: (message: string) => Effect.Effect<void> }
>() {}

class Database extends Context.Tag("Database")<
  Database,
  { query: (sql: string) => Effect.Effect<User[]> }
>() {}

使用服务

const program = Effect.gen(function* (_) {
  const logger = yield* _(Logger)
  const db = yield* _(Database)

  yield* _(logger.log("Fetching users"))
  const users = yield* _(db.query("SELECT * FROM users"))
  return users
})

提供实现

import { Layer } from "effect"

const LoggerLive = Layer.succeed(Logger, {
  log: (msg) => Effect.sync(() => console.log(msg))
})

const DatabaseLive = Layer.succeed(Database, {
  query: (sql) => Effect.tryPromise(() => fetchDb(sql))
})

const programWithDeps = program.pipe(
  Effect.provide(LoggerLive),
  Effect.provide(DatabaseLive)
)

Layer:服务组合

Layer 是 Effect 的”服务工厂”,负责创建和清理资源。

import { Layer, Effect } from "effect"

// 创建带生命周期的服务
const DatabaseLive = Layer.scoped(Database, Effect.gen(function* (_) {
  const conn = yield* _(openConnection())
  yield* _(Effect.addFinalizer(() => closeConnection(conn)))
  return { query: (sql) => executeQuery(conn, sql) }
}))

组合 Layer

import { Layer } from "effect"

// 合并多个 Layer
const ServicesLive = Layer.merge(LoggerLive, DatabaseLive)

// 依赖其他 Layer
const AppLive = Layer.provide(ServicesLive, ConfigLive)

Stream:异步流

import { Stream, Effect } from "effect"

const stream = Stream.range(1, 10).pipe(
  Stream.map((n) => n * 2),
  Stream.filter((n) => n > 5),
  Stream.tap((n) => Effect.log(`processing ${n}`))
)

// 运行
Effect.runPromise(Stream.runCollect(stream))

Schedule:调度

import { Schedule, Effect } from "effect"

// 指数退避
const exponential = Schedule.exponential("100 millis")

// 最多重试 5 次
const withRetry = Schedule.recurs(5)

// 组合
const schedule = exponential.pipe(
  Schedule.whileOutput((delay) => delay < 10000)
)

const program = Effect.tryPromise(() => fetch("/api")).pipe(
  Effect.retry(schedule)
)

Schema:验证和序列化

import { Schema } from "effect"

const User = Schema.Struct({
  id: Schema.Number,
  name: Schema.String,
  email: Schema.optional(Schema.String),
  role: Schema.Literal("admin", "user", "guest")
})

type User = Schema.Schema.Type<typeof User>

// 解析
const result = Schema.decodeUnknown(User)({ id: 1, name: "Alice", role: "admin" })

// 序列化
const json = Schema.encode(User)({ id: 1, name: "Alice", role: "admin" })

实战模式

HTTP 服务

import { Effect, Layer } from "effect"

// 定义服务接口
class HttpClient extends Context.Tag("HttpClient")<
  HttpClient,
  {
    get: <T>(url: string) => Effect.Effect<T, HttpError>
    post: <T>(url: string, body: unknown) => Effect.Effect<T, HttpError>
  }
>() {}

// 实现
const HttpClientLive = Layer.succeed(HttpClient, {
  get: (url) => Effect.tryPromise({
    try: () => fetch(url).then(r => r.json()),
    catch: (e) => new HttpError(e)
  }),
  post: (url, body) => Effect.tryPromise({
    try: () => fetch(url, { method: "POST", body: JSON.stringify(body) }).then(r => r.json()),
    catch: (e) => new HttpError(e)
  })
})

业务逻辑

import { Effect } from "effect"

const getUserProfile = (userId: number) => Effect.gen(function* (_) {
  const http = yield* _(HttpClient)
  const cache = yield* _(Cache)

  // 先查缓存
  const cached = yield* _(cache.get(`user:${userId}`), Effect.orElse(() => Effect.succeed(null)))

  if (cached) return cached

  // 缓存未命中,请求 API
  const user = yield* _(http.get<User>(`/api/users/${userId}`))

  // 写入缓存
  yield* _(cache.set(`user:${userId}`, user, "5 minutes"))

  return user
})

迁移策略

渐进式采用

  1. 先从复杂部分开始 - 错误处理多的模块
  2. 不需要知道所有 API - 80% 的收益来自几个核心函数
  3. 可以和 Promise 共存 - Effect 可以转换为 Promise

常用迁移模式

// Promise -> Effect
const fromPromise = Effect.tryPromise(() => somePromise())

// Effect -> Promise
const toPromise = Effect.runPromise(effect)

// 包装现有代码
const wrapSync = Effect.try(() => mightThrow())
const wrapAsync = Effect.tryPromise(() => mightReject())

生态系统

说明
effect核心库
@effect/platform平台抽象(Node、Browser、Bun)
@effect/platform-nodeNode.js 特定实现
@effect/platform-bunBun 特定实现
@effect/sqlSQL 数据库抽象
@effect/httpHTTP 服务器/客户端
@effect/cliCLI 应用框架
@effect/printer打印和格式化
@effect/typeclass类型类(Functor、Monad 等)

性能

  • 核心运行时:~15KB(压缩 + tree-shake)
  • 按需增长:用多少加载多少
  • V8 优化友好:避免常见陷阱

如果你最终用了 100KB 的 Effect 代码,同样功能不用 Effect 可能是 1MB。


学习曲线

是的,有学习曲线。 但:

  • 不需要知道函数式编程——可以当作”智能 Promise”使用
  • 80% 的收益来自少数几个函数
  • 类型系统会引导你

资源


总结

Effect 解决了 TypeScript/JavaScript 生态的根本问题:缺少标准库

它提供:

  • 类型安全的错误处理
  • 资源管理
  • 依赖注入
  • 异步流
  • 数据验证

渐进式采用:不需要一次性重写整个应用。从复杂部分开始,逐步扩展。

📝 文章反馈

你的反馈能帮助我写出更好的文章