533 words
3 minutes
[Effect Layers] 10. 从Effect.Service提取默认层
2025-08-30 18:20:17
2025-12-24 23:45:46

从Effect.Service提取默认层#

https://github.com/typeonce-dev/effect-getting-started-course

最后,我们也可以重构 PokeApi

const make = Effect.gen(function* () {
  const pokemonCollection = yield* PokemonCollection;
  const buildPokeApiUrl = yield* BuildPokeApiUrl;

  return {
    getPokemon: Effect.gen(function* () {
      const requestUrl = buildPokeApiUrl({ name: pokemonCollection[0] });

      const response = yield* Effect.tryPromise({
        try: () => fetch(requestUrl),
        catch: () => new FetchError(),
      });

      if (!response.ok) {
        return yield* new FetchError();
      }

      const json = yield* Effect.tryPromise({
        try: () => response.json(),
        catch: () => new JsonError(),
      });

      return yield* Schema.decodeUnknown(Pokemon)(json);
    }),
  };
});

export class PokeApi extends Context.Tag("PokeApi")<
  PokeApi,
  Effect.Effect.Success<typeof make>
>() {
  static readonly Live = Layer.effect(this, make).pipe(
    Layer.provide(Layer.mergeAll(PokemonCollection.Live, BuildPokeApiUrl.Live))
  );
}

由于服务被定义为 Effect(使用 Layer.effect),我们使用 effect

export class PokeApi extends Effect.Service<PokeApi>()("PokeApi", {
  effect: Effect.gen(function* () {
    const pokemonCollection = yield* PokemonCollection;
    const buildPokeApiUrl = yield* BuildPokeApiUrl;

    return {
      getPokemon: Effect.gen(function* () {
        const requestUrl = buildPokeApiUrl({ name: pokemonCollection[0] });

        const response = yield* Effect.tryPromise({
          try: () => fetch(requestUrl),
          catch: () => new FetchError(),
        });

        if (!response.ok) {
          return yield* new FetchError();
        }

        const json = yield* Effect.tryPromise({
          try: () => response.json(),
          catch: () => new JsonError(),
        });

        return yield* Schema.decodeUnknown(Pokemon)(json);
      }),
    };
  }),
}) {}

然后我们还添加 dependencies 来提供所需的服务。

使用 Effect.Service 定义的 class 具有一个 Default 属性,该属性包含提供了所有依赖的默认层。

我们从 PokemonCollectionBuildPokeApiUrl 中使用它:

export class PokeApi extends Effect.Service<PokeApi>()("PokeApi", {
  effect: Effect.gen(function* () {
    const pokemonCollection = yield* PokemonCollection;
    const buildPokeApiUrl = yield* BuildPokeApiUrl;

    return {
      getPokemon: Effect.gen(function* () {
        const requestUrl = buildPokeApiUrl({ name: pokemonCollection[0] });

        const response = yield* Effect.tryPromise({
          try: () => fetch(requestUrl),
          catch: () => new FetchError(),
        });

        if (!response.ok) {
          return yield* new FetchError();
        }

        const json = yield* Effect.tryPromise({
          try: () => response.json(),
          catch: () => new JsonError(),
        });

        return yield* Schema.decodeUnknown(Pokemon)(json);
      }),
    };
  }),
  dependencies: [PokemonCollection.Default, BuildPokeApiUrl.Default],
}) {}
NOTE

DefaultWithoutDependencies

Effect.Service 还有一个 DefaultWithoutDependencies 属性,该属性包含不包含依赖的默认层(例如 BuildPokeApiUrl.DefaultWithoutDependencies)。

我们在 MainLayer 中也提取 PokeApi.Default 来完成对 Effect.Service 的重构:

index.ts

import { Effect, Layer } from "effect";
import { PokeApi } from "./PokeApi";

const MainLayer = Layer.mergeAll(PokeApi.Default);

export const program = Effect.gen(function* () {
  const pokeApi = yield* PokeApi;
  return yield* pokeApi.getPokemon;
});

const runnable = program.pipe(Effect.provide(MainLayer));

const main = runnable.pipe(
  Effect.catchTags({
    FetchError: () => Effect.succeed("Fetch error"),
    JsonError: () => Effect.succeed("Json error"),
    ParseError: () => Effect.succeed("Parse error"),
  })
);

Effect.runPromise(main).then(console.log);

现在应用程序的工作方式与之前相同,但使用了更具表现力的API,代码行数更少。


所有这些 LayerConfig 的优势是什么?

这种实现使所有服务都具有可组合性。我们将在下一个关于测试的模块中看到可组合性的实际应用 🚀

[Effect Layers] 10. 从Effect.Service提取默认层
https://0bipinnata0.my/posts/course/effect-beginners-complete-getting-started/layers/10-extracting-default-layer-from-effect-service/
Author
0bipinnata0
Published at
2025-08-30 18:20:17