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.
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>
// ▼
constfailure= Effect.fail(
newError("Operation failed due to network error")
)
@see ― succeed to create an effect that represents a successful value.
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.
Logs one or more messages or error causes at the current log level.
Details
This function provides a simple way to log messages or error causes during
the execution of your effects. By default, logs are recorded at the INFO
level, but this can be adjusted using other logging utilities
(Logger.withMinimumLogLevel). Multiple items, including Cause instances,
can be logged in a single call. When logging Cause instances, detailed
error information is included in the log output.
The log output includes useful metadata like the current timestamp, log
level, and fiber ID, making it suitable for debugging and tracking purposes.
This function does not interrupt or alter the effect's execution flow.
While handling errors one at a time by their _tag is very easy to do by default, this level of granularity can become difficult to manage as the number of errors in your application grows. Often, you simply want to group errors into “categories” which you can discriminate against.
Outside of Effect, inheritance is the common pattern by which to implement this. Errors in a category share a base class, and instanceof can be used to discriminate:
abstractclass
classCategoryA
CategoryAextends
var Error:ErrorConstructor
Error {}
abstractclass
classCategoryB
CategoryBextends
var Error:ErrorConstructor
Error {}
class
classFooError
FooErrorextends
classCategoryA
CategoryA {}
class
classBarError
BarErrorextends
classCategoryA
CategoryA {}
class
classBazError
BazErrorextends
classCategoryB
CategoryB {}
try {
// ...
} catch (
var error:unknown
error) {
if (
var error:unknown
errorinstanceof
classCategoryA
CategoryA) {
// do A
} elseif (
var error:unknown
errorinstanceof
classCategoryB
CategoryB) {
// do B
} else {
throw
var error:unknown
error;
}
}
However this doesn’t work in Effect because javascript does not support multiple inheritance, and we are already extending the TaggedError class from Effect! We could try to make an intermediate class that extends TaggedError that we can again extend from, but there’s a small problem: the types for TaggedError are straight up wizardy. Good luck making a fully functional generic wrapper for that.
Mixins, despite their fancy name, are remarkably simple. They are basically functions that take in a class and return a new class. Surprisingly this is a pattern that has a whole page to it’s own in the official typescript docs.
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() andconsole.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and process.stderr. The global console can be used without callingrequire('console').
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3) (the arguments are all passed to util.format()).
constcount=5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format() for more information.
@since ― v0.1.100
log(
conste:MyError
e.
function (Anonymousclass).message2: string
message2); // "hihi"
There’s a couple cool things about this. First is that everything is fully typed- typescript can infer the type of the class returned from the mixin and merge it with any class you extend from it, and we can even provide constraints on what classes can be passed into the mixin.
Also because a mixin is just a function, we can do all sorts of cool functional things like composition. I’m sure you’ve heard of “composition over inheritance” before and this pattern is that saying to a T.
Additionally, because mixins are just functions, we can use our favorite utility from Effect: pipe
import {
functionpipe<A>(a:A):A (+19overloads)
Pipes the value of an expression into a pipeline of functions.
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"
constresult=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.
Pipes the value of an expression into a pipeline of functions.
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"
constresult=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.
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() andconsole.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and process.stderr. The global console can be used without callingrequire('console').
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stdout with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3) (the arguments are all passed to util.format()).
Checks whether a value is an object containing a specified property key.
@since ― 2.0.0
hasProperty(
x: A
x,
sym: Category extends symbol
sym);
};
Next, using that guard we can write a catchCategory function which works just like catchTag but on categories instead of tags. Just like catchTag it properly narrows the output type to not include caught errors:
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<
function (typeparameter) Bin <E, Categoryextendssymbol, B, E2, R2>(category:Category, f: (error:Extract<E, Record<Category, any>>) =>Effect.Effect<B, E2, R2>): <A, R>(effect:Effect.Effect<A, E, R>) =>Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
function (typeparameter) Ain <A, R>(effect:Effect.Effect<A, E, R>):Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
A,
function (typeparameter) Rin <A, R>(effect:Effect.Effect<A, E, R>):Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
R>(
effect: Effect.Effect<A, E, R>
effect:
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<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<
function (typeparameter) Ain <A, R>(effect:Effect.Effect<A, E, R>):Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
A,
function (typeparameter) Ein <E, Categoryextendssymbol, B, E2, R2>(category:Category, f: (error:Extract<E, Record<Category, any>>) =>Effect.Effect<B, E2, R2>): <A, R>(effect:Effect.Effect<A, E, R>) =>Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
E,
function (typeparameter) Rin <A, R>(effect:Effect.Effect<A, E, R>):Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
R>,
):
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<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<
function (typeparameter) Ain <A, R>(effect:Effect.Effect<A, E, R>):Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
A|
function (typeparameter) Bin <E, Categoryextendssymbol, B, E2, R2>(category:Category, f: (error:Extract<E, Record<Category, any>>) =>Effect.Effect<B, E2, R2>): <A, R>(effect:Effect.Effect<A, E, R>) =>Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
B,
typeExclude<T, U> =TextendsU?never:T
Exclude from T those types that are assignable to U
Exclude<
function (typeparameter) Ein <E, Categoryextendssymbol, B, E2, R2>(category:Category, f: (error:Extract<E, Record<Category, any>>) =>Effect.Effect<B, E2, R2>): <A, R>(effect:Effect.Effect<A, E, R>) =>Effect.Effect<A|B, Exclude<E, Record<Category, any>> |E2, R|R2>
E,
typeRecord<Kextendskeyofany, T> = { [PinK]:T; }
Construct a type with a set of properties K of type T
Recovers from specific errors based on a predicate.
When to Use
catchIf works similarly to
catchSome
, but it allows you to
recover from errors by providing a predicate function. If the predicate
matches the error, the recovery effect is applied. This function doesn't
alter the error type, so the resulting effect still carries the original
error type unless a user-defined type guard is used to narrow the type.
Example (Catching Specific Errors with a Predicate)
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.
Represents an effect that does nothing and produces no value.
When to Use
Use this effect when you need to represent an effect that does nothing.
This is useful in scenarios where you need to satisfy an effect-based
interface or control program flow without performing any operations. For
example, it can be used in situations where you want to return an effect
from a function but do not need to compute or return any result.
Logs one or more messages or error causes at the current log level.
Details
This function provides a simple way to log messages or error causes during
the execution of your effects. By default, logs are recorded at the INFO
level, but this can be adjusted using other logging utilities
(Logger.withMinimumLogLevel). Multiple items, including Cause instances,
can be logged in a single call. When logging Cause instances, detailed
error information is included in the log output.
The log output includes useful metadata like the current timestamp, log
level, and fiber ID, making it suitable for debugging and tracking purposes.
This function does not interrupt or alter the effect's execution flow.
This pattern is pretty neat, decently practical and doesn’t impact any of the existing _tag ways of interacting with errors.
Check it out and let me know what you think. Here is a link to a full example in the Effect playground.
Thank you to Tim Smart for his feedback on this pattern and for authoring the catchCategory function.
UPDATE: Michael Arnaldi shared his own version of this pattern that works with string literals and has some other niceties like being able to catch multiple categories at once