Effect-TS:TypeScript 缺失的标准库
2026/3/22
TypeScript 函数式编程 Effect-TS
Effect-TS:TypeScript 缺失的标准库
TypeScript/JavaScript 是最受欢迎的编程语言,但仍然缺少标准库。Effect 正在填补这个空白。
为什么需要 Effect?
2022 State of JavaScript 调查
最想要的 JS 特性:
| 排名 | 特性 | Effect 支持 |
|---|---|---|
| 2 | Standard Library | ✅ 核心 |
| 4 | Immutable Data Structures | ✅ Effect/Data |
| 5 | Observable | ✅ Effect/Stream |
| 6 | Pipe Operator | ✅ Effect/Function |
| 8 | Pattern 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 对比
| 特性 | Promise | Effect |
|---|---|---|
| 错误类型 | any | 类型安全 |
| 取消 | ❌ | ✅ |
| 重试 | 手动 | 内置 |
| 超时 | 手动 | 内置 |
| 资源管理 | try/finally | Scope |
| 依赖注入 | 手动 | 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
})
迁移策略
渐进式采用
- 先从复杂部分开始 - 错误处理多的模块
- 不需要知道所有 API - 80% 的收益来自几个核心函数
- 可以和 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-node | Node.js 特定实现 |
@effect/platform-bun | Bun 特定实现 |
@effect/sql | SQL 数据库抽象 |
@effect/http | HTTP 服务器/客户端 |
@effect/cli | CLI 应用框架 |
@effect/printer | 打印和格式化 |
@effect/typeclass | 类型类(Functor、Monad 等) |
性能
- 核心运行时:~15KB(压缩 + tree-shake)
- 按需增长:用多少加载多少
- V8 优化友好:避免常见陷阱
如果你最终用了 100KB 的 Effect 代码,同样功能不用 Effect 可能是 1MB。
学习曲线
是的,有学习曲线。 但:
- 不需要知道函数式编程——可以当作”智能 Promise”使用
- 80% 的收益来自少数几个函数
- 类型系统会引导你
资源
- 官网
- 文档
- Discord 社区 - 2000+ 成员
- GitHub
总结
Effect 解决了 TypeScript/JavaScript 生态的根本问题:缺少标准库。
它提供:
- 类型安全的错误处理
- 资源管理
- 依赖注入
- 异步流
- 数据验证
渐进式采用:不需要一次性重写整个应用。从复杂部分开始,逐步扩展。