Effect TS: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 85: Line 85:
</syntaxhighlight>
</syntaxhighlight>
=Handling errors=
=Handling errors=
So this took a bit of time to figure out but you know me, one example I understand a good to go. Here I implement '''catchAll''' and '''catchTag'''. The later has changed since the video. I chose to stick with generators as they look better. So the video did not show me how either. I this example I wrapped the getPokemon in catchAll but with the individual gone error It after where the error could occur.<br>
==For errors==
You just make a unique tag and good to go
<syntaxhighlight lang="ts">
class FetchError {
    readonly _tag = "FetchError"
}
class DecodeError {
    readonly _tag = "DecodeError"
}


class SameWeightError {
    readonly _tag = "SameWeightError"
    constructor(readonly weight: number) {}
}
</syntaxhighlight>
==For CatchTag==
We added the catch inline with the call.
<syntaxhighlight lang="ts">
<syntaxhighlight lang="ts">
const calculateHeaviestPokemonC = (pokemons: Pokemon[]) =>
    Effect.reduce(pokemons, 0, (highest, pokemon) =>
        pokemon.weight === highest
            ? Effect.fail(new SameWeightError(pokemon.weight))
            : Effect.succeed(pokemon.weight > highest ? pokemon.weight : highest)
    )
const programC = Effect.gen(function* (_) {
    const arr = yield* _(getRandomNumberArray)
    const pokemons = yield* _(Effect.all(arr.map(getPokemonC)))
    yield* _(Effect.log("\n" + pokemons.map( (pokemon) => `${pokemon.name} ${pokemon.weight}`).join("\n")))
    const heaviestWeight = yield* _(calculateHeaviestPokemonC(pokemons))
    Effect.catchTag(
        calculateHeaviestPokemonC(pokemons),
        "SameWeightError", (err: SameWeightError) =>
            Effect.log(`Two pokemons have the same weight: ${err.weight}`)
        ),
   
    yield* _(Effect.log(`The heaviest Pokemon weighs: ${heaviestWeight}`))
})
</syntaxhighlight>
==For CatchAll==
We separated out the functions and assigned the right error
<syntaxhighlight lang="ts">
const getPokemonC = (id: number) =>
    Effect.catchAll(
        Effect.gen(function* (_) {
            const res = yield* _(
                Effect.tryPromise({
                    try: () => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`),
                    catch: () => new FetchError(),
                }))
            return yield* _(           
                Effect.tryPromise({
                    try: () => (res.json()),
                    catch: () => new DecodeError(),
                })
            )
        }
    ),
    () => Effect.succeed({ name: "Unknown", weight: 0 }),
)
</syntaxhighlight>
</syntaxhighlight>

Revision as of 00:48, 24 June 2025

Introduction

Dipped my toe into this. It seems to be a combination of RxJs and Microsoft DI. Not sure why it is worthwhile using types rather than a typed language. If all you have is TS resource then it makes sense. Make change my mind.

First Program

And its not hello world

import { Console, Effect, pipe, Schema } from 'effect';

const Pokemon = Schema.Struct({
  name: Schema.String,
  weight: Schema.Number,
});

type Pokemon = Schema.Schema.Type<typeof Pokemon>;

const getPokemon = (id: number) => 
    pipe(
       Effect.tryPromise({
        try: () => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
            .then(response => response.json()),
        catch: (unknown) => new Error(`Failed to fetch Pokemon with id ${id}, Error %s ${unknown}`),
        }),
        Effect.flatMap((data) => Schema.decodeUnknown(Pokemon)((data))),
    )

const getRandomNumberArray = Effect.all(
    Array.from({ length: 10 }, () => 
        Effect.sync(
            () => Math.floor(Math.random() * 100) +1
        )
    )
)

 const calculateHeaviestPokemon = (pokemons: Pokemon[]) => 
    Effect.reduce(pokemons, 0, (highest, pokemon) =>
        pokemon.weight === highest 
            ? Effect.fail(new Error("Two pokemons have the same weight"))
            : Effect.succeed(pokemon.weight > highest ? pokemon.weight : highest)
    )

const program = pipe(
    getRandomNumberArray,
    Effect.flatMap((arr) =>
        Effect.all(
            arr.map((arr) => getPokemon(arr))
        )
    ),
    Effect.tap((pokemons) =>
    Effect.log("\n" + pokemons.map((pokemon) => `${pokemon.name} ${pokemon.weight}`).join("\n"))),
    Effect.flatMap((pokemon) => calculateHeaviestPokemon(pokemon)),
    Effect.flatMap((heaviestWeight) => 
        Effect.log(`The heaviest Pokemon weighs: ${heaviestWeight}`)    
    ),
)

Effect.runPromise(program).then(Console.log)

Second Program

Same bat time same bat channel but this time with generators

const getPokemonB = (id: number) => 
    Effect.gen(function* (_) {
        const res = yield* _(
            Effect.tryPromise({
                try: () => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
                    .then(response => response.json()),
                catch: (unknown) => new Error(`Failed to fetch Pokemon with id ${id}, Error %s ${unknown}`),
            }),
        )
        return yield* _(Schema.decodeUnknown(Pokemon)((res)))
    }
)


const programB = Effect.gen(function* (_) {
    const arr = yield* _(getRandomNumberArray)
    const pokemons = yield* _(Effect.all(arr.map(getPokemonB)))
    yield* _(Effect.log("\n" + pokemons.map( (pokemon) => `${pokemon.name} ${pokemon.weight}`).join("\n")))

    const heaviestWeight = yield* _(calculateHeaviestPokemon(pokemons))
    yield* _(Effect.log(`The heaviest Pokemon weighs: ${heaviestWeight}`))
})

Effect.runPromise(programB).then(Console.log)

Handling errors

So this took a bit of time to figure out but you know me, one example I understand a good to go. Here I implement catchAll and catchTag. The later has changed since the video. I chose to stick with generators as they look better. So the video did not show me how either. I this example I wrapped the getPokemon in catchAll but with the individual gone error It after where the error could occur.

For errors

You just make a unique tag and good to go

class FetchError {
    readonly _tag = "FetchError"
}

class DecodeError {
    readonly _tag = "DecodeError"
}

class SameWeightError {
    readonly _tag = "SameWeightError"
    constructor(readonly weight: number) {}
}

For CatchTag

We added the catch inline with the call.

const calculateHeaviestPokemonC = (pokemons: Pokemon[]) => 
    Effect.reduce(pokemons, 0, (highest, pokemon) =>
        pokemon.weight === highest 
            ? Effect.fail(new SameWeightError(pokemon.weight))
            : Effect.succeed(pokemon.weight > highest ? pokemon.weight : highest)
    )

const programC = Effect.gen(function* (_) {
    const arr = yield* _(getRandomNumberArray)
    const pokemons = yield* _(Effect.all(arr.map(getPokemonC)))
    yield* _(Effect.log("\n" + pokemons.map( (pokemon) => `${pokemon.name} ${pokemon.weight}`).join("\n")))
    const heaviestWeight = yield* _(calculateHeaviestPokemonC(pokemons))

    Effect.catchTag(
        calculateHeaviestPokemonC(pokemons),
         "SameWeightError", (err: SameWeightError) =>
            Effect.log(`Two pokemons have the same weight: ${err.weight}`)
         ),
    
    yield* _(Effect.log(`The heaviest Pokemon weighs: ${heaviestWeight}`))
})

For CatchAll

We separated out the functions and assigned the right error

const getPokemonC = (id: number) => 
    Effect.catchAll(
        Effect.gen(function* (_) {
            const res = yield* _(

                Effect.tryPromise({
                    try: () => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`),
                    catch: () => new FetchError(),
                }))

            return yield* _(            
                Effect.tryPromise({
                    try: () => (res.json()),
                    catch: () => new DecodeError(),
                })
            )
        }
    ),
    () => Effect.succeed({ name: "Unknown", weight: 0 }),
)