The first benefit of effect is error handling.
However, Effect.promise does not handle errors.
const main = Effect.flatMap(
fetchRequest,
jsonResponse
);
Effect.runPromise(main);When you use Effect.promise effect assumes that the operation can never fail. In practice main works just like Promise<unknown> but wrapped in Effect.
If the function inside
Effect.promisethrows, then executing the effect will throw as well! 👇
> effect-getting-started-course@1.0.0 dev
> tsx src/index.ts
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
SyntaxError: Unexpected token 'N', "Not Found" is not valid JSONSince we know that fetch can fail we need another function: Effect.tryPromise.
import { Effect } from "effect";
/// 👇 Effect<Response, UnknownException>
const fetchRequest = Effect.tryPromise(
() => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);Whereas Effect.promise returned an Effect<Response>, Effect.tryPromise instead collects any error and returns Effect<Response, UnknownException>.
Types for errors
What is UnknownException? Why Effect has 2 generic parameters now?
When you execute any plain typescript function you have no way of knowing what may go wrong unless you read the function implementation:
const request: Promise<Response> = fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");Simple example: how do you know if fetch can fail? request is Promise<Response>, so no idea if something can go wrong, or how.
This problem becomes even worst when you compose multiple functions.
const main: Promise<unknown> =
fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
.then((response) => response.json());Since both request and json may fail you are left with two choices:
- Read the implementation of all the functions and check for any
throw, then wrap each function with try/catch
const main = async (): Promise<unknown> => {
let response;
try {
response = await fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");
} catch (e) {
// Error with fetch: Do something here
return;
}
try {
return response.json();
} catch (e) {
// Error with json: Do something here
return;
}
};- Wrap everything in a single
try/catchand report a generic "Some error happened"
const main = async (): Promise<unknown> => {
try {
const response = await fetch("https://pokeapi.co/api/v2/pokemon/garchomp/");
return response.json();
} catch (e) {
// Some error somewhere 💁🏼♂️
return;
}
};Effect solves this problem by providing the error directly in the type:
const fetchRequest: Effect<Response, UnknownException> = Effect.tryPromise(
() => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);When we compose fetchRequest and jsonResponse we get a program main that returns Effect<unknown, UnknownException>:
unknownis the return type of callingresponse.json()when everything works as expectedUnknownExceptionis the error type when eitherfetchRequestorjsonResponsefails
/// Effect<Response, UnknownException>
const fetchRequest = Effect.tryPromise(
() => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/")
);
/// Effect<unknown, UnknownException>
const jsonResponse = (response: Response) => Effect.tryPromise(
() => response.json()
);
/// Effect<unknown, UnknownException>
const main = Effect.flatMap(fetchRequest, jsonResponse);We still cannot distinguish between errors in
fetchRequestandjsonResponsesince both are typed asUnknownException.Don't worry!
tryPromisealso allows to define custom errors, we are going to learn how in the following lessons.
