Next problem: we get a generic UnknownException as error, which doesn't tell us much about what went wrong.
We cannot distinguish between errors caused by
fetchRequestfrom errors onjsonResponse, since their errors have the sameUnknownExceptiontype.
That's why in effect we usually define our own custom errors. The only "requirement" is making them Tagged Errors, which means having a _tag parameter used as discriminator.
In its simplest form an error is just an object with a _tag key with a string literal value:
const fetchError = {
// 👇 String literal "FetchError" (`as const`), a type of `string` won't work!
_tag: "FetchError" as const
};The type of this error object can be represented with an interface:
interface FetchError {
readonly _tag: "FetchError";
}
const fetchError: FetchError = { _tag: "FetchError" };While this is the simplest way to understand tagged errors, it's not the recommended way of defining errors.
For now this works. In the next lessons we are going to learn about
Data.TaggedError, which is a more powerful method to collect information about errors.
tryPromise with custom errors
The tryPromise function adds a generic UnknownException when no custom error is provided.
We can provide our own error by using the try/catch function overload of Effect.tryPromise:
In typescript is possible to give multiple signatures to the same function (Function overloading).
tryPromisecan accept a single function (with a genericUnknownException) or an object withtry/catchto specify a custom error.
import { Effect } from "effect";
interface FetchError {
readonly _tag: "FetchError";
}
/// 👇 Effect<Response, FetchError>
const fetchRequest = Effect.tryPromise({
try: () => fetch("https://pokeapi.co/api/v2/psadokemon/garchomp/"),
catch: (): FetchError => ({ _tag: "FetchError" }),
});Now fetchRequest has our custom FetchError as error.
We can do the same with jsonResponse:
import { Effect } from "effect";
interface FetchError {
readonly _tag: "FetchError";
}
interface JsonError {
readonly _tag: "JsonError";
}
const fetchRequest = Effect.tryPromise({
try: () => fetch("https://pokeapi.co/api/v2/psadokemon/garchomp/"),
catch: (): FetchError => ({ _tag: "FetchError" }),
});
const jsonResponse = (response: Response) =>
/// 👇 Effect<unknown, JsonError>
Effect.tryPromise({
try: () => response.json(),
catch: (): JsonError => ({ _tag: "JsonError" }),
});The great thing about effects is that composing functions with different errors will automatically accumulate all the errors in the error type.
Now our final main function has a union of possible errors:
/// Effect<unknown, FetchError | JsonError>
const main = fetchRequest.pipe(
Effect.flatMap(jsonResponse)
);
FetchError | JsonErrormeans thatmaincan fail withFetchErrororJsonError.Notice how this type was inferred by composing a function that has
FetchErrorand another withJsonError=FetchError | JsonError
