名称: effect-patterns-getting-started 描述: Effect-TS入门模式。在Effect-TS应用中入门时使用此技能。
Effect-TS模式:入门
此技能提供了6个精心挑选的Effect-TS入门模式。 在以下任务相关时使用此技能:
- 入门
- Effect-TS应用中的最佳实践
- 真实世界模式和解决方案
🟢 入门模式
使用Effect.retry重试失败的操作
规则: 使用Effect.retry重试失败的操作。
好示例:
import { Effect, Schedule, pipe } from "effect";
class ApiError {
readonly _tag = "ApiError";
constructor(readonly status: number) {}
}
const fetchUserData = (userId: string) =>
Effect.tryPromise({
try: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new ApiError(response.status);
return response.json();
},
catch: (error) => error as ApiError,
});
// 重试最多3次,每次间隔500毫秒
const fetchWithRetry = (userId: string) =>
pipe(
fetchUserData(userId),
Effect.retry(
Schedule.recurs(3).pipe(Schedule.addDelay(() => "500 millis"))
),
Effect.catchAll((error) =>
Effect.succeed({ error: `重试后失败: ${error._tag}` })
)
);
原理:
使用Effect.retry来自动重试失败的Effect。结合Schedule来控制重试次数和重试间隔。
网络请求会失败。数据库会超时。服务会暂时宕机。 与其立即失败,通常你希望重试几次。 Effect让这变成一行代码。
你好世界:你的第一个Effect程序
规则: 使用Effect.succeed创建你的第一个Effect程序。
好示例:
import { Effect } from "effect";
// 步骤1:创建一个成功带有值的Effect
const helloWorld = Effect.succeed("你好,Effect!");
// 步骤2:运行Effect并获取结果
const result = Effect.runSync(helloWorld);
console.log(result); // "你好,Effect!"
原理:
使用Effect.succeed包装一个值来创建你的第一个Effect,然后使用Effect.runSync运行它以查看结果。
每个旅程都从"你好世界"开始。在Effect中,你通过描述你想要发生的事情来创建计算,然后运行它们。这种分离正是Effect强大的地方。
使用Effect.map变换值
规则: 使用map变换Effect值。
好示例:
import { Effect } from "effect";
// 从一个成功带有数字的Effect开始
const getNumber = Effect.succeed(5);
// 变换它:乘以2
const doubled = Effect.map(getNumber, (n) => n * 2);
// 再次变换:转换为字符串
const asString = Effect.map(doubled, (n) => `结果是 ${n}`);
// 运行以查看结果
const result = Effect.runSync(asString);
console.log(result); // "结果是 10"
原理:
使用Effect.map来变换Effect内部的成功值。变换函数接收值并返回一个新值。
就像Array.map变换数组元素一样,Effect.map变换Effect的成功值。这让你可以构建变换管道,而无需在最后之前运行任何内容。
使用Effect.fail和catchAll处理第一个错误
规则: 使用Effect.fail和catchAll处理错误。
好示例:
import { Effect, pipe } from "effect";
class UserNotFound {
readonly _tag = "UserNotFound";
constructor(readonly id: string) {}
}
const findUser = (id: string) =>
id === "123"
? Effect.succeed({ id, name: "Alice" })
: Effect.fail(new UserNotFound(id));
const program = pipe(
findUser("456"),
Effect.catchTag("UserNotFound", (e) =>
Effect.succeed({ id: e.id, name: "Guest" })
),
Effect.map((user) => `你好,${user.name}!`)
);
const result = Effect.runSync(program);
console.log(result); // "你好,Guest!"
原理:
使用Effect.fail创建一个失败带有错误的Effect,并使用Effect.catchAll从该失败中恢复。
真实程序会失败。Effect在类型系统中使失败显式化,这样你就不会忘记处理它们。与try/catch不同,Effect错误在类型中被追踪。
使用Effect.all并行运行多个Effect
规则: 使用Effect.all并行运行多个Effect。
好示例:
import { Effect, pipe } from "effect";
// 模拟从不同源获取数据
const fetchUser = Effect.succeed({ id: 1, name: "Alice" }).pipe(
Effect.delay("100 millis")
);
const fetchPosts = Effect.succeed([
{ id: 1, title: "你好世界" },
{ id: 2, title: "Effect太棒了" },
]).pipe(Effect.delay("150 millis"));
const fetchSettings = Effect.succeed({ theme: "dark" }).pipe(
Effect.delay("50 millis")
);
// 并行获取所有数据
const program = Effect.gen(function* () {
const [user, posts, settings] = yield* Effect.all(
[fetchUser, fetchPosts, fetchSettings],
{ concurrency: "unbounded" }
);
yield* Effect.log(`加载了 ${user.name},有 ${posts.length} 个帖子`);
return { user, posts, settings };
});
Effect.runPromise(program);
原理:
使用Effect.all来并发运行多个Effect并等待它们全部完成。默认情况下,Effect顺序运行——添加concurrency选项以并行运行它们。
真实应用经常需要同时做多件事——从几个API获取数据、处理多个文件等。Effect.all让你自然地表达这一点,没有回调地狱或复杂的Promise.all模式。
为什么用Effect?Effect与Promise的比较
规则: 理解为什么Effect比原始Promise更好。
原理:
Effect解决了三个Promise没有的问题:
- 错误被类型化 - 你确切知道什么可能出错
- 依赖被追踪 - 你知道需要什么服务
- Effect是惰性的 - 在你说运行之前,什么都不运行