Table of Contents
Effect
Random effect stuff
Installing effect
Follow this page to install effect: https://effect.website/docs/v4/getting-started/installation
Follow this page to install the effect LSP: https://effect.website/docs/v4/getting-started/devtools
Basics
The effect type
┌─── Represents the success type
│ ┌─── Represents the error type
│ │ ┌─── Represents required dependencies
▼ ▼ ▼
Effect<Success, Error, Requirements>
Effect models three ways a computation can finish, tracked (partially) in the Effect<A, E, R> type signature
Success
The effect completes normally and produces a value of type A
const success = Effect.succeed(42);
// Effect<number, never, never>
Fail
An expected typed error. Something you declared up front and plan to handle. It shows up in the E channel. Failures are recoverable: Effect.catchAll, Effect.catchTag, etc. can intercept them.
const failure = Effect.fail(new Error("Something went wrong"));
// Effect<never, Error, never>
Die
An unexpected error. It's deliberately not tracked in the E type parameter, since it isn't something callers are meant to plan around.
const defect = Effect.die(new Error("Unexpected!"));
// Effect<never, never, never>
Ordinary error handling (catchAll, catchTag) lets defects pass straight through. You need Effect.catchAllDefect to reach them.
Running an effect
import { Effect } from "effect";
const program = Effect.gen(function* () {
console.log("Hello, World!");
return 1;
});
const result = Effect.runSync(program);
// Output: Hello, World!
console.log(result);
result; // => 1
Services
Defining a service
Define a service with Context.Tag, then provide a live implementation with Layer.
import { Context, Data, Effect, Layer } from "effect";
// Simple Pokemon type (just the basics)
export interface Pokemon {
id: number;
name: string;
height: number;
weight: number;
types: Array<{
type: {
name: string;
};
}>;
sprites: {
front_default: string | null;
};
}
/** Errors **/
export class FetchError extends Data.TaggedError("FetchError")<{}> {}
export class JsonError extends Data.TaggedError("JsonError")<{}> {}
/** Service Definition **/
export class PokeApi extends Context.Tag("PokeApi")<
PokeApi,
{
readonly getPokemon: (
id: number,
) => Effect.Effect<Pokemon, FetchError | JsonError>;
}
>() {}
/** Live Implementation **/
export const PokeApiLive = Layer.succeed(PokeApi, {
getPokemon: (id: number) =>
Effect.gen(function* () {
const response = yield* Effect.tryPromise({
try: () => fetch(`https://pokeapi.co/api/v2/pokemon/${id}/`),
catch: () => new FetchError(),
});
if (!response.ok) {
return yield* Effect.fail(new FetchError());
}
return yield* Effect.tryPromise({
try: () => response.json() as Promise<Pokemon>,
catch: () => new JsonError(),
});
}),
});
Using a service
Effect.gen(function* () {
const pokeApi = yield* PokeApi;
return yield* pokeApi.getPokemon(input.id);
}).pipe(Effect.provide(PokeApiLive));
Splitting a service
If you need to split a services implementation across multiple files use this:
type GetPokemon = (typeof PokeApi.Service)["getPokemon"];
export const getPokemon: GetPokemon = (id) =>Effect.gen(function* () {
// ...
}
Helpers
Handle all errors
Use this effect helper to make sure all errors need to be handled
import { Effect, Exit, Cause } from "effect";
export async function runEffect<T>(
effect: Effect.Effect<T, never>,
): Promise<T> {
const exit = await Effect.runPromiseExit(effect);
if (Exit.isFailure(exit)) {
console.error(Cause.pretty(exit.cause));
throw Cause.squash(exit.cause);
}
return exit.value;
}
usage:
return await runEffect(
Effect.gen(function* () {
// some effect
}).pipe(
Effect.catchTags({
// handle errors
FetchError: () => Effect.die(errors.FETCH_ERROR()),
}),
),
);
Related snippets
How to use oRPC with Effect
A basic example on how to use oRPC with effect