It is required to handle errors in Stanzas. Failure to
appropriately catch known errors results in static type analysis failing at
Sonnet creation:
declareconst
const canFail: Effect.Effect<void, Error,never>
canFail:
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<out A,out E =never,out R =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.
NOTE: side-effect handlers are provided up-front to guarantee declarative
behavior.
@example
import{ configureStore, Tuple as RTKTuple }from"@reduxjs/toolkit"
import{ Sonnet, Stanza }from"redux-sonnet"
import{ Stream, Layer }from"effect"
const stanza = Stanza.fromStream(Stream.make(
{ type:"ACTION-1"},
{ type:"ACTION-2"},
{ type:"ACTION-3"},
))
const sonnet = Sonnet.make(
// ^?
stanza,
Sonnet.defaultLayer,
)
const store =configureStore({
// ^?
reducer:()=>{},
middleware:()=>newRTKTuple(sonnet)
})
@since ― 0.0.0
make(
canFail,
Error ts(2379) ― Argument of type 'Effect<void, Error, never>' is not assignable to parameter of type 'Effect<void, never, SonnetService>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Type 'Error' is not assignable to type 'never'.
Pipes the value of an expression into a pipeline of functions.
When to Use
This is useful in combination with data-last functions as a simulation of
methods:
as.map(f).filter(g)
becomes:
import{ pipe, Array }from"effect"
pipe(as, Array.map(f), Array.filter(g))
Details
The pipe function is a utility that allows us to compose functions in a
readable and sequential manner. It takes the output of one function and
passes it as the input to the next function in the pipeline. This enables us
to build complex transformations by chaining multiple functions together.
import{ pipe }from"effect"
const result =pipe(input, func1, func2,..., funcN)
In this syntax, input is the initial value, and func1, func2, ...,
funcN are the functions to be applied in sequence. The result of each
function becomes the input for the next function, and the final result is
returned.
It's important to note that functions passed to pipe must have a single
argument because they are only called with a single argument.
@example
// Example: Chaining Arithmetic Operations
import{ pipe }from"effect"
// Define simple arithmetic operations
constincrement=(x:number)=> x +1
constdouble=(x:number)=> x *2
constsubtractTen=(x:number)=> x -10
// Sequentially apply these operations using `pipe`
const result =pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2
@since ― 2.0.0
pipe}from"effect"
importtype{
type Action<T extendsstring=string>={
type: T;
}
An action is a plain object that represents an intention to change the
state. Actions are the only way to get data into the store. Any data,
whether from UI events, network callbacks, or other sources such as
WebSockets needs to eventually be dispatched as actions.
Actions must have a type field that indicates the type of action being
performed. Types can be defined as constants and imported from another
module. These must be strings, as strings are serializable.
Other than type, the structure of an action object is really up to you.
If you're interested, check out Flux Standard Action for recommendations on
how actions should be constructed.
Action,
type Reducer<S =any, A extends Action = UnknownAction, PreloadedState = S>=(state: S | PreloadedState |undefined,action: A)=> S
A reducer is a function that accepts
an accumulation and a value and returns a new accumulation. They are used
to reduce a collection of values down to a single value
Reducers are not unique to Redux—they are a fundamental concept in
functional programming. Even most non-functional languages, like
JavaScript, have a built-in API for reducing. In JavaScript, it's
Array.prototype.reduce().
In Redux, the accumulated value is the state object, and the values being
accumulated are actions. Reducers calculate a new state given the previous
state and an action. They must be pure functions—functions that return
the exact same output for given inputs. They should also be free of
side-effects. This is what enables exciting features like hot reloading and
time travel.
Reducers are the most important concept in Redux.
Do not put API calls into reducers.
Reducer,
(alias) interfaceUnknownAction
importUnknownAction
An Action type which accepts any other properties.
This is mainly for the use of the Reducer type.
This is not part of Action itself to prevent types that extend Action from
having an index signature.
UnknownAction}from"redux"
import{
import Actions
Actions,
import Operators
Operators,
import Sonnet
Sonnet}from"redux-sonnet"
class
classFetchError
FetchErrorextends
import Data
Data.
constTaggedError:<"FetchError">(tag:"FetchError")=>new<A>(args: Equals<A,{}>extendstrue?void:{readonly [P inkeyof A as P extends"_tag"?never: P]: A[P];})=> YieldableError &{
Creates an Effect that represents an asynchronous computation that might
fail.
When to Use
In situations where you need to perform asynchronous operations that might
fail, such as fetching data from an API, you can use the tryPromise
constructor. This constructor is designed to handle operations that could
throw exceptions by capturing those exceptions and transforming them into
manageable errors.
Error Handling
There are two ways to handle errors with tryPromise:
If you don't provide a catch function, the error is caught and the
effect fails with an UnknownException.
If you provide a catch function, the error is caught and the catch
function maps it to an error of type E.
Interruptions
An optional AbortSignal can be provided to allow for interruption of the
wrapped Promise API.
@see ― promise if the effectful computation is asynchronous and does not throw errors.
@example
// Title: Fetching a TODO Item
import{ Effect }from"effect"
constgetTodo=(id:number)=>
// Will catch any errors and propagate them as UnknownException
Pipes the value of an expression into a pipeline of functions.
When to Use
This is useful in combination with data-last functions as a simulation of
methods:
as.map(f).filter(g)
becomes:
import{ pipe, Array }from"effect"
pipe(as, Array.map(f), Array.filter(g))
Details
The pipe function is a utility that allows us to compose functions in a
readable and sequential manner. It takes the output of one function and
passes it as the input to the next function in the pipeline. This enables us
to build complex transformations by chaining multiple functions together.
import{ pipe }from"effect"
const result =pipe(input, func1, func2,..., funcN)
In this syntax, input is the initial value, and func1, func2, ...,
funcN are the functions to be applied in sequence. The result of each
function becomes the input for the next function, and the final result is
returned.
map takes a function and applies it to the value contained within an
effect, creating a new effect with the transformed value.
It's important to note that effects are immutable, meaning that the original
effect is not modified. Instead, a new effect is returned with the updated
value.
@see ― mapError for a version that operates on the error channel.
@see ― mapBoth for a version that operates on both channels.
@see ― flatMap or andThen for a version that can return a new effect.
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.
Details
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.
@example
import{ pipe, Effect }from"effect"
// Function to apply a discount safely to a transaction amount
constapplyDiscount= (
total:number,
discountRate:number
):Effect.Effect<number, Error>=>
discountRate ===0
? Effect.fail(newError("Discount rate cannot be zero"))
constasVoid:<A, E, R>(self: Effect.Effect<A, E, R>)=> Effect.Effect<void, E, R>
This function maps the success value of an Effect value to void. If the
original Effect value succeeds, the returned Effect value will also
succeed. If the original Effect value fails, the returned Effect value
will fail with the same error.
Catches and handles specific errors by their _tag field, which is used as a
discriminator.
When to Use
catchTag is useful when your errors are tagged with a readonly _tag field
that identifies the error type. You can use this function to handle specific
error types by matching the _tag value. This allows for precise error
handling, ensuring that only specific errors are caught and handled.
The error type must have a readonly _tag field to use catchTag. This
field is used to identify and match errors.
@see ― catchTags for a version that allows you to handle multiple error
types at once.
constasVoid:<A, E, R>(self: Effect.Effect<A, E, R>)=> Effect.Effect<void, E, R>
This function maps the success value of an Effect value to void. If the
original Effect value succeeds, the returned Effect value will also
succeed. If the original Effect value fails, the returned Effect value
will fail with the same error.
An action is a plain object that represents an intention to change the
state. Actions are the only way to get data into the store. Any data,
whether from UI events, network callbacks, or other sources such as
WebSockets needs to eventually be dispatched as actions.
Actions must have a type field that indicates the type of action being
performed. Types can be defined as constants and imported from another
module. These must be strings, as strings are serializable.
Other than type, the structure of an action object is really up to you.
If you're interested, check out Flux Standard Action for recommendations on
how actions should be constructed.
Action,
type Reducer<S =any, A extends Action = UnknownAction, PreloadedState = S>=(state: S | PreloadedState |undefined,action: A)=> S
A reducer is a function that accepts
an accumulation and a value and returns a new accumulation. They are used
to reduce a collection of values down to a single value
Reducers are not unique to Redux—they are a fundamental concept in
functional programming. Even most non-functional languages, like
JavaScript, have a built-in API for reducing. In JavaScript, it's
Array.prototype.reduce().
In Redux, the accumulated value is the state object, and the values being
accumulated are actions. Reducers calculate a new state given the previous
state and an action. They must be pure functions—functions that return
the exact same output for given inputs. They should also be free of
side-effects. This is what enables exciting features like hot reloading and
time travel.
Reducers are the most important concept in Redux.
Do not put API calls into reducers.
Reducer,
(alias) interfaceUnknownAction
importUnknownAction
An Action type which accepts any other properties.
This is mainly for the use of the Reducer type.
This is not part of Action itself to prevent types that extend Action from
having an index signature.
UnknownAction}from"redux"
import{
import Actions
Actions,
import Operators
Operators,
import Sonnet
Sonnet}from"redux-sonnet"
class
classFetchError
FetchErrorextends
import Data
Data.
constTaggedError:<"FetchError">(tag:"FetchError")=>new<A>(args: Equals<A,{}>extendstrue?void:{readonly [P inkeyof A as P extends"_tag"?never: P]: A[P];})=> YieldableError &{
Creates an Effect that represents an asynchronous computation that might
fail.
When to Use
In situations where you need to perform asynchronous operations that might
fail, such as fetching data from an API, you can use the tryPromise
constructor. This constructor is designed to handle operations that could
throw exceptions by capturing those exceptions and transforming them into
manageable errors.
Error Handling
There are two ways to handle errors with tryPromise:
If you don't provide a catch function, the error is caught and the
effect fails with an UnknownException.
If you provide a catch function, the error is caught and the catch
function maps it to an error of type E.
Interruptions
An optional AbortSignal can be provided to allow for interruption of the
wrapped Promise API.
@see ― promise if the effectful computation is asynchronous and does not throw errors.
@example
// Title: Fetching a TODO Item
import{ Effect }from"effect"
constgetTodo=(id:number)=>
// Will catch any errors and propagate them as UnknownException
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.
@example
import{ Effect }from"effect"
constaddServiceCharge=(amount:number)=> amount +1
constapplyDiscount= (
total:number,
discountRate:number
):Effect.Effect<number, Error>=>
discountRate ===0
? Effect.fail(newError("Discount rate cannot be zero"))
constasVoid:<A, E, R>(self: Effect.Effect<A, E, R>)=> Effect.Effect<void, E, R>
This function maps the success value of an Effect value to void. If the
original Effect value succeeds, the returned Effect value will also
succeed. If the original Effect value fails, the returned Effect value
will fail with the same error.
Catches and handles specific errors by their _tag field, which is used as a
discriminator.
When to Use
catchTag is useful when your errors are tagged with a readonly _tag field
that identifies the error type. You can use this function to handle specific
error types by matching the _tag value. This allows for precise error
handling, ensuring that only specific errors are caught and handled.
The error type must have a readonly _tag field to use catchTag. This
field is used to identify and match errors.
@see ― catchTags for a version that allows you to handle multiple error
types at once.
This function maps the success value of an Effect value to void. If the
original Effect value succeeds, the returned Effect value will also
succeed. If the original Effect value fails, the returned Effect value
will fail with the same error.