380 words
2 minutes
[Effect Layers] 05. 层间依赖
2025-08-30 18:20:12
2025-12-24 23:45:46

层间依赖#

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

你可能在运行 main 时注意到了一个类型错误:

https://vmfiooakcvcmnormoutn.supabase.co/storage/v1/object/public/sandromaglione-com/blog-images/missing-dependency-issue-effect-layers

我们仍然无法运行main,因为似乎缺少一个依赖关系!

Type 'PokeApiUrl' is not assignable to type 'never'.

这是一个缺失的依赖关系!为什么 PokeApiUrl 缺失?我们不是已经向 Layer.mergeAll 提供了 PokeApiUrl 吗?

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live,
  PokeApiUrl.Live
);

是的,但我们有一个没有解决的层之间的依赖关系BuildPokeApiUrl 层依赖于 PokeApiUrl

BuildPokeApiUrl.ts

export class BuildPokeApiUrl extends Context.Tag("BuildPokeApiUrl")<
  BuildPokeApiUrl,
  ({ name }: { name: string }) => string
>() {
  static readonly Live = Layer.effect(
    this,
    Effect.gen(function* () {
      const pokeApiUrl = yield* PokeApiUrl; // 👈 创建依赖
      return ({ name }) => `${pokeApiUrl}/${name}`;
    })
  );
}

如果我们检查 BuildPokeApiUrl.Live 的类型,我们会看到以下内容:Layer.Layer<BuildPokeApiUrl, never, PokeApiUrl>

由于完整的服务需要 PokeApiUrl,我们需要直接BuildPokeApiUrl 提供它。

我们通过使用 Layer.provide 来做到这一点:

index.ts

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live.pipe(Layer.provide(PokeApiUrl.Live)),
  PokeApiUrl.Live
);
IMPORTANT

API 区别

我们在组合层时使用 Layer.provide,在提供最终层来运行Effect时使用 Effect.provide

Layer.provideEffect.provide 是两个不同的API!

现在我们的程序可以编译并工作了:

index.ts

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

const MainLayer = Layer.mergeAll(
  PokeApi.Live,
  PokemonCollection.Live,
  BuildPokeApiUrl.Live.pipe(Layer.provide(PokeApiUrl.Live)),
  PokeApiUrl.Live
);

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);
> effect-getting-started-course@1.0.0 dev
> BASE_URL=https://pokeapi.co tsx src/index.ts

{ id: 120, order: 191, name: 'staryu', height: 8, weight: 345 }