API request with effect

Let's come back to our API request:

const main = async () => {
  const response = await fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");
  const json = await response.json();
  return json;
};

main().then(console.log);

The program can be divided in 2 steps:

  1. Execute the fetch request
  2. Extract json from the response
const fetchRequest = () => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");

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

const main = async () => {
  const response = await fetchRequest(); // 1️⃣
  const json = await jsonResponse(response); // 2️⃣
  return json;
};

main().then(console.log);

Just like with plain typescript, with effect we need to use different APIs when working with async or sync functions.

Therefore, the first thing to consider is that these are both async operations. For those we can use Effect.promise.

Effect.promise is a wrapper around any async operation. It returns an Effect containing the result of calling the async function.

The equivalent to Effect.promise for synchronous operations is Effect.sync.

Converting from native typescript to effect only requires wrapping your async operation with Effect.promise:

import { Effect } from "effect";

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

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

Notice how we are precisely wrapping a Promise inside Effect.promise:

// 👇 Promise<Response>
const promiseRequest = () => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");

/// 👇 Effect<Response>
const fetchRequest = Effect.promise(promiseRequest);

Same for response.json():

import { Effect } from "effect";

/// 👇 Promise<unknown>
const jsonResponse = (response: Response) => response.json(); 

/// 👇 Effect<unknown>
const jsonResponse = (response: Response) => Effect.promise( 
  () => response.json() 
); 

We went from Promise<Response> to Effect<Response>:

  • Promise<Response> is eager (async operation executed immediately)
  • Effect<Response> is lazy (it requires explicit execution and can be executed as many times as needed)

Understand Effect

What we did since now is wrapping a native API with Effect.promise:

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

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

We said before that effect is a composition of steps:

  1. () => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
  2. () => response.json()

Based on which API we use, we are telling Effect how to execute the wrapped function.

Effect.promise is saying: "Execute this function asynchronously and return its result"

But how is that any different from what we were doing before? What are the benefits?

In the following lessons we will explore how we can compose effects together. This compositional quality of effects allows to define error handling, timeouts, filtering, and more.

Building an app with effect is all about defining how to handle each step, and then compose them together.

Let's see next how to handle the composition part.