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:
- Execute the
fetch
request - 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 isEffect.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>
toEffect<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:
() => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
() => 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.