flatMap: Composing effects

An Effect is a full description of a program. We can compose Effect with each other to build other Effect that describe more complex programs.

In this example we have 2 effects fetchRequest and jsonResponse:

  • fetchRequest returns an effect that contains Response
  • We want to extract Response from the first effect and provide it to jsonResponse

The problem is that we wrapped Response inside Effect (Effect<Response>). We cannot just "extract" Response anymore:

const fetchRequest = Effect.promise(() =>
  fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);

const response: Response = /// No way to directly access `Response` 😬

Effects are computations. They don't hold the Response.

It is impossible to get the Response out of an effect because the Response does not exist (yet).

Evaluating the effect will result in a Response.

Instead effect provides functions that describe what's the next step to execute based on the value from the previous effect(s).

flatMap: Get value and return Effect

Effect.flatMap allows to access the result of an effect and chain another Effect:

  • First parameter: Effect from where we want to extract the value
  • Second parameter: function that gives access to the first Effect parameter and returns another Effect
const fetchRequest = Effect.promise(() =>
  fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);

const main = Effect.flatMap(
  fetchRequest, // 👈 Extract value from `fetchRequest`
  (response) => // 👈 Access `Response` and return another `Effect`
);

With Effect.flatMap we can chain jsonResponse since we get access to response:

/// 👇 `Effect<Response>`
const fetchRequest = Effect.promise(() =>
  fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);

/// 👇 `Effect<unknown>` (return type of `jsonResponse`)
const main = Effect.flatMap(
  fetchRequest,
  (response) => jsonResponse(response)
);

This may look weird. No more linear imperative code one line after the other, but instead using functions that take as input other functions and return functions 🙌

Bare with me for now, effect offers some way to write way better-looking code that we are going to introduce later.

We can then run the final program using runPromise:

runPromise is the equivalent of runSync but for asynchronous programs

const fetchRequest = Effect.promise(() =>
  fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);

const jsonResponse = (response: Response) =>
  Effect.promise(() => response.json());

const main = Effect.flatMap(
  fetchRequest,
  (response) => jsonResponse(response)
);

Effect.runPromise(main);

We can even simplify the code to:

const fetchRequest = Effect.promise(() =>
  fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);

const jsonResponse = (response: Response) =>
  Effect.promise(() => response.json());

const main = Effect.flatMap(
  fetchRequest,
  jsonResponse
);

Effect.runPromise(main);
Effect Playground