Similar to errors, we can remove the dependency from the type by providing a concrete implementation before running the effect.
We reached the point in which we actually need to create a concrete implementation of the service. A service created using Context
has a .of
method that allows to create an instance of the service.
Remember the getPokemon
function we refactored before? Let's use it to create the PokeApi
service:
export const PokeApi = Context.GenericTag<PokeApi>("PokeApi");
// 👉 `PokeApi.of` defines a concrete implementation for the service
export const PokeApiLive = PokeApi.of({
getPokemon: Effect.gen(function* () {
const baseUrl = yield* Config.string("BASE_URL");
const response = yield* Effect.tryPromise({
try: () => fetch(`${baseUrl}/api/v2/pokemon/garchomp/`),
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);
}),
});
We can create as many concrete implementations as we want from the same abstract service by using
.of
.By convention the production implementation has a
-Live
suffix. You can then create a-Test
/-Mock
/-Dev
implementation if needed.export const PokeApi = Context.GenericTag<PokeApi>("PokeApi");> export const PokeApiTest = PokeApi.of({ getPokemon: Effect.succeed({ id: 1, height: 10, weight: 10, order: 1, name: "myname", }), });
Now we need to provide PokeApiLive
to program
to run the app. We use Effect.provideService
to do this:
- First parameter is the service
Context
definition - Second parameter is the concrete implementation of the service
import { Effect } from "effect";
import { PokeApi, PokeApiLive } from "./PokeApi";
const program = Effect.gen(function* () {
const pokeApi = yield* PokeApi;
return yield* pokeApi.getPokemon;
});
const runnable = program.pipe(Effect.provideService(PokeApi, PokeApiLive));
The type of runnable
is now Effect<Pokemon, FetchError | JsonError | ParseError | ConfigError, never>
: we provided the dependency on PokeApi
, removed it from the type, and now the third parameter is never
.
We can now compose the full app and run it without errors:
import { Effect } from "effect";
import { PokeApi, PokeApiLive } from "./PokeApi";
const program = Effect.gen(function* () {
const pokeApi = yield* PokeApi;
return yield* pokeApi.getPokemon;
});
const runnable = program.pipe(Effect.provideService(PokeApi, PokeApiLive));
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);
program
: FullEffect
implementation with errors and dependencies included in the typerunnable
: Provide all the dependencies toprogram
to make the third type parameternever
main
: Handle all (or part of) the errors fromrunnable
to make the second type parameternever