Here is an example how to handle structured error payloads using Fx-Fetch.
It is good practice to have structured error payloads from your backend API.
This allows you to make decisions based on error codes and additional details provided in the error response.
For example, consider the following error payload returned from the backend:
1
{
2
"error": {
3
"code": "BOOK_NOT_FOUND", // or "AUTHOR_NOT_FOUND"
4
"message": "The book with the given ID does not exist.",
5
}
6
}
We would like to handle these structured errors through the Effect error channel.
But we want to avoid boilerplate code for parsing the error response manually for each request.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
@since ― 2.0.0
@since ― 2.0.0
Effect<unknown>,
5
errorCode: string
errorCode:string,
6
onError: ()=> unknown
onError:()=>unknown
7
):
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interface Effect<outA, outE=never, outR=never>
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
@since ― 2.0.0
@since ― 2.0.0
Effect<unknown> {
8
// TODO: Implement
9
}
But type signatures are not correct yet. If we want to have proper types, we need to use generics 😍.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
Handles all errors in an effect by providing a fallback effect.
Details
This function catches any errors that may occur during the execution of an
effect and allows you to handle them by specifying a fallback effect. This
ensures that the program continues without failing by recovering from errors
using the provided fallback logic.
Note: This function only handles recoverable errors. It will not recover
from unrecoverable defects.
Example (Providing Recovery Logic for Recoverable Errors)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
Effect.gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
Handles all errors in an effect by providing a fallback effect.
Details
This function catches any errors that may occur during the execution of an
effect and allows you to handle them by specifying a fallback effect. This
ensures that the program continues without failing by recovering from errors
using the provided fallback logic.
Note: This function only handles recoverable errors. It will not recover
from unrecoverable defects.
Example (Providing Recovery Logic for Recoverable Errors)
Creates an Effect that represents a recoverable error.
When to Use
Use this function to explicitly signal an error in an Effect. The error
will keep propagating unless it is handled. You can handle the error with
functions like
catchAll
or
catchTag
.
Example (Creating a Failed Effect)
import { Effect } from"effect"
// ┌─── Effect<never, Error, never>
// ▼
const failure = Effect.fail(
newError("Operation failed due to network error")
)
@see ― succeed to create an effect that represents a successful value.
@since ― 2.0.0
fail(
selfErr: E| Response.NotOkError
selfErr)),// Reassign original error if parsing fails
flatMap lets you sequence effects so that the result of one effect can be
used in the next step. It is similar to flatMap used with arrays but works
specifically with Effect instances, allowing you to avoid deeply nested
effect structures.
Since effects are immutable, flatMap always returns a new effect instead of
changing the original one.
When to Use
Use flatMap when you need to chain multiple effects, ensuring that each
step produces a new Effect while flattening any nested effects that may
occur.
Example
import { pipe, Effect } from"effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total:number,
discountRate:number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(newError("Discount rate cannot be zero"))
Creates an Effect that represents a recoverable error.
When to Use
Use this function to explicitly signal an error in an Effect. The error
will keep propagating unless it is handled. You can handle the error with
functions like
catchAll
or
catchTag
.
Example (Creating a Failed Effect)
import { Effect } from"effect"
// ┌─── Effect<never, Error, never>
// ▼
const failure = Effect.fail(
newError("Operation failed due to network error")
)
@see ― succeed to create an effect that represents a successful value.
@since ― 2.0.0
fail) // Fail with the custom error
34
)
35
)
36
);
37
}
Add dual API support
Adding dual API support is super simple because of the EffectTS Function module.
Unfortunately only for runtime (JavaScript).
The typescript types must be provided manually.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
Handles all errors in an effect by providing a fallback effect.
Details
This function catches any errors that may occur during the execution of an
effect and allows you to handle them by specifying a fallback effect. This
ensures that the program continues without failing by recovering from errors
using the provided fallback logic.
Note: This function only handles recoverable errors. It will not recover
from unrecoverable defects.
Example (Providing Recovery Logic for Recoverable Errors)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
Effect.gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
Handles all errors in an effect by providing a fallback effect.
Details
This function catches any errors that may occur during the execution of an
effect and allows you to handle them by specifying a fallback effect. This
ensures that the program continues without failing by recovering from errors
using the provided fallback logic.
Note: This function only handles recoverable errors. It will not recover
from unrecoverable defects.
Example (Providing Recovery Logic for Recoverable Errors)
Creates an Effect that represents a recoverable error.
When to Use
Use this function to explicitly signal an error in an Effect. The error
will keep propagating unless it is handled. You can handle the error with
functions like
catchAll
or
catchTag
.
Example (Creating a Failed Effect)
import { Effect } from"effect"
// ┌─── Effect<never, Error, never>
// ▼
const failure = Effect.fail(
newError("Operation failed due to network error")
)
@see ― succeed to create an effect that represents a successful value.
@since ― 2.0.0
fail(
selfErr: E| Response.NotOkError
selfErr)),// Re-assign original error if parsing fails
flatMap lets you sequence effects so that the result of one effect can be
used in the next step. It is similar to flatMap used with arrays but works
specifically with Effect instances, allowing you to avoid deeply nested
effect structures.
Since effects are immutable, flatMap always returns a new effect instead of
changing the original one.
When to Use
Use flatMap when you need to chain multiple effects, ensuring that each
step produces a new Effect while flattening any nested effects that may
occur.
Example
import { pipe, Effect } from"effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total:number,
discountRate:number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(newError("Discount rate cannot be zero"))
Creates an Effect that represents a recoverable error.
When to Use
Use this function to explicitly signal an error in an Effect. The error
will keep propagating unless it is handled. You can handle the error with
functions like
catchAll
or
catchTag
.
Example (Creating a Failed Effect)
import { Effect } from"effect"
// ┌─── Effect<never, Error, never>
// ▼
const failure = Effect.fail(
newError("Operation failed due to network error")
)
@see ― succeed to create an effect that represents a successful value.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
Creates a function that can be used in a data-last (aka pipeable) or
data-first style.
The first parameter to dual is either the arity of the uncurried function
or a predicate that determines if the function is being used in a data-first
or data-last style.
Using the arity is the most common use case, but there are some cases where
you may want to use a predicate. For example, if you have a function that
takes an optional argument, you can use a predicate to determine if the
function is being used in a data-first or data-last style.
You can pass either the arity of the uncurried function or a predicate
which determines if the function is being used in a data-first or
data-last style.
Example (Using arity to determine data-first or data-last style)
import { dual, pipe } from"effect/Function"
const sum = dual<
(that:number)=>(self:number)=> number,
(self:number, that:number)=> number
>(2, (self, that)=> self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5
Example (Using call signatures to define the overloads)
As you can see, most of the code is related to EffectTS itself.
Fx-Fetch gives you a freedom to focus on handling the error payloads
without thinking about how to not shoot yourself in the foot 🦶🔫.