gen: Write linear code with effect

As we add more steps to pipe the code starts to become less readable.

const main = fetchRequest.pipe(
  Effect.filterOrFail(
    (response) => response.ok,
    () => new FetchError()
  ),
  Effect.flatMap(jsonResponse),
  Effect.catchTags({
    FetchError: () => Effect.succeed("Fetch error"),
    JsonError: () => Effect.succeed("Json error"),
  })
);

We went from a simple if check, to a scary looking filterOrFail with two parameters and more lines of code.

Fear not! There is a solution for this in effect called Effect.gen:

const main = Effect.gen(function* () {
  const response = yield* fetchRequest;
  if (!response.ok) {
    return yield* new FetchError();
  }

  return yield* jsonResponse(response);
});

If you look closely, this is very similar to a normal async/await typescript function:

const fetchRequest = () => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");
const jsonResponse = (response: Response) => response.json();

const main = async () => {
  const response = await fetchRequest();
  if (!response.ok) {
    throw new FetchError();
  }

  return await jsonResponse(response);
};

Take this and change:

  • async with Effect.gen
  • Arrow function () => with a generator function function* ()
  • await and throw with yield*
const main = async () => {
  const response = await fetchRequest();
  if (!response.ok) {
    throw new FetchError();
  }

  return await jsonResponse(response);
};

// 👆 Spot the differences 👇

const main = Effect.gen(function* () {
  const response = yield* fetchRequest;
  if (!response.ok) {
    return yield* new FetchError();
  }

  return yield* jsonResponse(response);
});

How generators work

No need to learn the details of how this works to use effect. In fact, this is merely an implementation detail.

For reference, Effect.gen uses a javascript feature called Generator functions.

This works in combination to the Iterator protocol to allow effect to track errors and success at every step in the function.

For what concerns us, this is a convenient way to write more concise and better-looking effect code, while keeping all the benefits of error tracking.

I suggest to favor Effect.gen over pipe for most use cases. That's what I do in my code as well 💁🏼‍♂️

From now on we are going to work with Effect.gen instead of pipe when possible.