In the context of programming, a service refers to a reusable component or functionality that can be used by different parts of an application.
Services are designed to provide specific capabilities and can be shared across multiple modules or components.
Services often encapsulate common tasks or operations that are needed by different parts of an application.
They can handle complex operations, interact with external systems or APIs, manage data, or perform other specialized tasks.
Services are typically designed to be modular and decoupled from the rest of the application.
This allows them to be easily maintained, tested, and replaced without affecting the overall functionality of the application.
When diving into services and their integration in application development, it helps to start from the basic principles of function management and dependency handling without relying on advanced constructs. Imagine having to manually pass a service around to every function that needs it:
This approach becomes cumbersome and unmanageable as your application grows, with services needing to be passed through multiple layers of functions.
To streamline this, you might consider using an environment object that bundles various services:
However, this introduces a new complexity: you must ensure that the environment is correctly set up with all necessary services before it’s used, which can lead to tightly coupled code and makes functional composition and testing more difficult.
Managing Services with Effect
The Effect library simplifies managing these dependencies by leveraging the type system.
Instead of manually passing services or environment objects around, Effect allows you to declare service dependencies directly in the function’s type signature using the Requirements parameter in the Effect type:
This is how it works in practice when using Effect:
Dependency Declaration: You specify what services a function needs directly in its type, pushing the complexity of dependency management into the type system.
Service Provision: Effect.provideService is used to make a service implementation available to the functions that need it. By providing services at the start, you ensure that all parts of your application have consistent access to the required services, thus maintaining a clean and decoupled architecture.
This approach abstracts away manual service handling, letting developers focus on business logic while the compiler ensures all dependencies are correctly managed. It also makes code more maintainable and scalable.
Let’s walk through managing services in Effect step by step:
Creating a Service: Define a service with its unique functionality and interface.
Using the Service: Access and utilize the service within your application’s functions.
Providing a Service Implementation: Supply an actual implementation of the service to fulfill the declared requirements.
How It Works
Up to this point, our examples with the Effect framework have dealt with effects that operate independently of external services.
This means the Requirements parameter in our Effect type signature has been set to never, indicating no dependencies.
However, real-world applications often need effects that rely on specific services to function correctly. These services are managed and accessed through a construct known as Context.
The Context serves as a repository or container for all services an effect may require.
It acts like a store that maintains these services, allowing various parts of your application to access and use them as needed.
The services stored within the Context are directly reflected in the Requirements parameter of the Effect type.
Each service within the Context is identified by a unique “tag,” which is essentially a unique identifier for the service.
When an effect needs to use a specific service, the service’s tag is included in the Requirements type parameter.
Creating a Service
To create a new service, you need two things:
A unique identifier.
A type describing the possible operations of the service.
Example (Defining a Random Number Generator Service)
Let’s create a service for generating random numbers.
Identifier. We’ll use the string "MyRandomService" as the unique identifier.
Type. The service type will have a single operation called next that returns a random number.
The exported Random value is known as a tag in Effect. It acts as a representation of the service and allows Effect to locate and use this service at runtime.
The service will be stored in a collection called Context, which can be thought of as a Map where the keys are tags and the values are services:
typeContext=Map<Tag, Service>
Let’s summarize the concepts we’ve covered so far:
Concept
Description
service
A reusable component providing specific functionality, used across different parts of an application.
tag
A unique identifier representing a service, allowing Effect to locate and use it.
context
A collection storing service, functioning like a map with tags as keys and services as values.
Using the Service
Now that we have our service tag defined, let’s see how we can use it by building a simple program.
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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.
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() and console.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 importing the node:console module.
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()).
In the code above, we can observe that we are able to yield the Random tag as if it were an effect itself.
This allows us to access the next operation of the service.
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect,
import Context
@since ― 2.0.0
@since ― 2.0.0
Context,
import Console
Console } from"effect"
2
3
// Declaring a tag for a service that generates random numbers
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Use andThen when you need to run multiple actions in sequence, with the
second action depending on the result of the first. This is useful for
combining effects or handling computations that must happen in order.
Details
The second action can be:
A constant value (similar to
as
)
A function returning a value (similar to
map
)
A Promise
A function returning a Promise
An Effect
A function returning an Effect (similar to
flatMap
)
Note:andThen works well with both Option and Either types,
treating them as effects.
@example
// Title: Applying a Discount Based on Fetched Amount
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"))
Use andThen when you need to run multiple actions in sequence, with the
second action depending on the result of the first. This is useful for
combining effects or handling computations that must happen in order.
Details
The second action can be:
A constant value (similar to
as
)
A function returning a value (similar to
map
)
A Promise
A function returning a Promise
An Effect
A function returning an Effect (similar to
flatMap
)
Note:andThen works well with both Option and Either types,
treating them as effects.
@example
// Title: Applying a Discount Based on Fetched Amount
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"))
In the code above, we can observe that we are able to flat-map over the Random tag as if it were an effect itself.
This allows us to access the next operation of the service within the Effect.andThen callback.
It’s worth noting that the type of the program variable includes Random in the Requirements type parameter:
constprogram:Effect<void, never, Random>
This indicates that our program requires the Random service to be provided in order to execute successfully.
If we attempt to execute the effect without providing the necessary service we will encounter a type-checking error:
Example (Type Error Without Service Provision)
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect,
import Context
@since ― 2.0.0
@since ― 2.0.0
Context } from"effect"
2
3
// Declaring a tag for a service that generates random numbers
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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.
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() and console.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 importing the node:console module.
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()).
Executes an effect synchronously, running it immediately and returning the
result.
When to Use
Use runSync to run an effect that does not fail and does not include
any asynchronous operations.
If the effect fails or involves asynchronous work, it will throw an error,
and execution will stop where the failure or async operation occurs.
@see ― runSyncExit for a version that returns an Exit type instead of
throwing an error.
@example
// Title: Synchronous Logging
import { Effect } from"effect"
constprogram= Effect.sync(() => {
console.log("Hello, World!")
return1
})
constresult= Effect.runSync(program)
// Output: Hello, World!
console.log(result)
// Output: 1
@example
// Title: Incorrect Usage with Failing or Async Effects
import { Effect } from "effect"
try {
// Attempt to run an effect that fails
Effect.runSync(Effect.fail("my error"))
} catch (e) {
console.error(e)
}
// Output:
// (FiberFailure) Error: my error
try {
// Attempt to run an effect that involves async work
Effect.runSync(Effect.promise(() => Promise.resolve(1)))
} catch (e) {
console.error(e)
}
// Output:
// (FiberFailure) AsyncFiberException: Fiber #0 cannot be resolved synchronously. This is caused by using runSync on an effect that performs async work
@since ― 2.0.0
runSync(
constprogram:Effect.Effect<void, never, Random>
program)
18
/*
19
Argument of type 'Effect<void, never, Random>' is not assignable to parameter of type 'Effect<void, never, never>'.
20
Type 'Random' is not assignable to type 'never'.ts(2345)
21
*/
To resolve this error and successfully execute the program, we need to provide an actual implementation of the Random service.
In the next section, we will explore how to implement and provide the Random service to our program, enabling us to run it successfully.
Providing a Service Implementation
In order to provide an actual implementation of the Random service, we can utilize the Effect.provideService function.
Example (Providing a Random Number Implementation)
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect,
import Context
@since ― 2.0.0
@since ― 2.0.0
Context } from"effect"
2
3
// Declaring a tag for a service that generates random numbers
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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.
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() and console.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 importing the node:console module.
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()).
The provideService function is used to provide an actual
implementation for a service in the context of an effect.
This function allows you to associate a service with its implementation so
that it can be used in your program. You define the service (e.g., a random
number generator), and then you use provideService to link that
service to its implementation. Once the implementation is provided, the
effect can be run successfully without further requirements.
@see ― provide for providing multiple layers to an effect.
@example
import { Effect, Context } from"effect"
// Declaring a tag for a service that generates random numbers
Creates an Effect that represents a synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>
var Math:Math
An intrinsic object that provides basic mathematics functionality and constants.
Executes an effect and returns the result as a Promise.
When to Use
Use runPromise when you need to execute an effect and work with the
result using Promise syntax, typically for compatibility with other
promise-based code.
If the effect succeeds, the promise will resolve with the result. If the
effect fails, the promise will reject with an error.
@see ― runPromiseExit for a version that returns an Exit type instead of rejecting.
@example
// Title: Running a Successful Effect as a Promise
//Example: Handling a Failing Effect as a Rejected Promise
import { Effect } from "effect"
Effect.runPromise(Effect.fail("my error")).catch(console.error)
// Output:
// (FiberFailure) Error: my error
@since ― 2.0.0
runPromise(
construnnable:Effect.Effect<void, never, never>
runnable)
26
/*
27
Example Output:
28
random number: 0.8241872233134417
29
*/
In the code above, we provide the program we defined earlier with an implementation of the Random service.
We use the Effect.provideService function to associate the Random tag with its implementation, an object with a next operation that generates a random number.
Notice that the Requirements type parameter of the runnable effect is now never. This indicates that the effect no longer requires any service to be provided.
With the implementation of the Random service in place, we are able to run the program without any further requirements.
Extracting the Service Type
To retrieve the service type from a tag, use the Context.Tag.Service utility type.
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
When we require the usage of more than one service, the process remains similar to what we’ve learned in defining a service, repeated for each service needed.
Example (Using Random and Logger Services)
Let’s examine an example where we need two services, namely Random and Logger:
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect,
import Context
@since ― 2.0.0
@since ― 2.0.0
Context } from"effect"
2
3
// Declaring a tag for a service that generates random numbers
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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.
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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.
}) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Random>> (+1overload)
The provideService function is used to provide an actual
implementation for a service in the context of an effect.
This function allows you to associate a service with its implementation so
that it can be used in your program. You define the service (e.g., a random
number generator), and then you use provideService to link that
service to its implementation. Once the implementation is provided, the
effect can be run successfully without further requirements.
@see ― provide for providing multiple layers to an effect.
@example
import { Effect, Context } from"effect"
// Declaring a tag for a service that generates random numbers
Creates an Effect that represents a synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>
var Math:Math
An intrinsic object that provides basic mathematics functionality and constants.
}) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<...> (+1overload)
The provideService function is used to provide an actual
implementation for a service in the context of an effect.
This function allows you to associate a service with its implementation so
that it can be used in your program. You define the service (e.g., a random
number generator), and then you use provideService to link that
service to its implementation. Once the implementation is provided, the
effect can be run successfully without further requirements.
@see ― provide for providing multiple layers to an effect.
@example
import { Effect, Context } from"effect"
// Declaring a tag for a service that generates random numbers
Creates an Effect that represents a synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>
var console:Console
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() and console.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 importing the node:console module.
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()).
Alternatively, instead of calling provideService multiple times, we can combine the service implementations into a single Context and then provide the entire context using the Effect.provide function:
Example (Combining Service Implementations)
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect,
import Context
@since ― 2.0.0
@since ― 2.0.0
Context } from"effect"
2
22 collapsed lines
3
// Declaring a tag for a service that generates random numbers
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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 synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>
var Math:Math
An intrinsic object that provides basic mathematics functionality and constants.
Creates an Effect that represents a synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>
var console:Console
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() and console.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 importing the node:console module.
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()).
There are situations where we may want to access a service implementation only if it is available.
In such cases, we can use the Effect.serviceOption function to handle this scenario.
The Effect.serviceOption function returns an implementation that is available only if it is actually provided before executing this effect.
To represent this optionality it returns an Option of the implementation.
Example (Handling Optional Services)
To determine what action to take, we can use the Option.isNone function provided by the Option module. This function allows us to check if the service is available or not by returning true when the service is not available.
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect,
import Context
@since ― 2.0.0
@since ― 2.0.0
Context,
import Option
@since ― 2.0.0
@since ― 2.0.0
Option } from"effect"
2
3
// Declaring a tag for a service that generates random numbers
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
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.
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() and console.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 importing the node:console module.
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()).
In the code above, we can observe that the Requirements type parameter of the program effect is never, even though we are working with a service. This allows us to access something from the context only if it is actually provided before executing this effect.
When we run the program effect without providing the Random service:
Effect.runPromise(program).then(console.log)
// Output: -1
We see that the log message contains -1, which is the default value we provided when the service was not available.
However, if we provide the Random service implementation:
Effect.runPromise(
Effect.provideService(program, Random, {
next: Effect.sync(() => Math.random())
})
).then(console.log)
// Example Output: 0.9957979486841035
We can observe that the log message now contains a random number generated by the next operation of the Random service.
Handling Services with Dependencies
Sometimes a service in your application may depend on other services. To maintain a clean architecture, it’s important to manage these dependencies without making them explicit in the service interface. Instead, you can use layers to handle these dependencies during the service construction phase.
Example (Defining a Logger Service with a Configuration Dependency)
Consider a scenario where multiple services depend on each other. In this case, the Logger service requires access to a configuration service (Config).
The Effect interface defines a value that lazily describes a workflow or
job. The workflow requires some context R, and may fail with an error of
type E, or succeed with a value of type A.
Effect values model resourceful interaction with the outside world,
including synchronous, asynchronous, concurrent, and parallel interaction.
They use a fiber-based concurrency model, with built-in support for
scheduling, fine-grained interruption, structured concurrency, and high
scalability.
To run an Effect value, you need a Runtime, which is a type that is
capable of executing Effect values.
@since ― 2.0.0
@since ― 2.0.0
Effect<void, never,
classConfig
Config>
12
}
13
>() {}
To handle these dependencies in a structured way and prevent them from leaking into the service interfaces, you can use the Layer abstraction. For more details on managing dependencies with layers, refer to the Managing Layers page.