Skip to content

SynchronizedRef

SynchronizedRef<A> serves as a mutable reference to a value of type A. With it, we can store immutable data and perform updates atomically and effectfully.

The distinctive function in SynchronizedRef is updateEffect. This function takes an effectful operation and executes it to modify the shared state. This is the key feature setting SynchronizedRef apart from Ref.

In real-world applications, SynchronizedRef is useful when you need to execute effects, such as querying a database, and then update shared state based on the result. It ensures that updates happen sequentially, preserving consistency in concurrent environments.

Example (Concurrent Updates with SynchronizedRef)

In this example, we simulate fetching user ages concurrently and updating a shared state that stores the ages:

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import SynchronizedRef
SynchronizedRef
} from "effect"
// Simulated API to get user age
const
const getUserAge: (userId: number) => Effect.Effect<number, never, never>
getUserAge
= (
userId: number
userId
: number) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <number>(value: number) => Effect.Effect<number, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

@seefail to create an effect that represents a failure.

@example

// Title: Creating a Successful Effect
import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@since2.0.0

succeed
(
userId: number
userId
* 10).
Pipeable.pipe<Effect.Effect<number, never, never>, Effect.Effect<number, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<number, never, never>) => Effect.Effect<number, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
(10 -
userId: number
userId
))
const
const meanAge: Effect.Effect<number[], never, never>
meanAge
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<SynchronizedRef.SynchronizedRef<number[]>, never, never>> | YieldWrap<Effect.Effect<number[], never, never>> | YieldWrap<...>, number[]>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

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.

@example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
// Initialize a SynchronizedRef to hold an array of ages
const
const ref: SynchronizedRef.SynchronizedRef<number[]>
ref
= yield*
import SynchronizedRef
SynchronizedRef
.
const make: <number[]>(value: number[]) => Effect.Effect<SynchronizedRef.SynchronizedRef<number[]>, never, never>

@since2.0.0

make
<number[]>([])
// Helper function to log state before each effect
const
const log: <R, E, A>(label: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
log
= <
function (type parameter) R in <R, E, A>(label: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
,
function (type parameter) E in <R, E, A>(label: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
function (type parameter) A in <R, E, A>(label: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
>(
label: string
label
: string,
effect: Effect.Effect<A, E, R>
effect
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
interface Effect<out A, out E = never, out R = never>

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.

@since2.0.0

@since2.0.0

Effect
<
function (type parameter) A in <R, E, A>(label: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
function (type parameter) E in <R, E, A>(label: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
function (type parameter) R in <R, E, A>(label: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
>) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<A, E, R>> | YieldWrap<Effect.Effect<void, never, never>>, A>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

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.

@example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
const
const value: number[]
value
= yield*
import SynchronizedRef
SynchronizedRef
.
const get: <number[]>(self: SynchronizedRef.SynchronizedRef<number[]>) => Effect.Effect<number[], never, never>

@since2.0.0

get
(
const ref: SynchronizedRef.SynchronizedRef<number[]>
ref
)
yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const log: (...message: ReadonlyArray<any>) => Effect.Effect<void, never, never>

Logs one or more messages or error causes at the current log level, which is INFO by default. This function allows logging multiple items at once and can include detailed error information using Cause instances.

To adjust the log level, use the Logger.withMinimumLogLevel function.

@example

import { Cause, Effect } from "effect"
const program = Effect.log(
"message1",
"message2",
Cause.die("Oh no!"),
Cause.die("Oh uh!")
)
// Effect.runFork(program)
// Output:
// timestamp=... level=INFO fiber=#0 message=message1 message=message2 cause="Error: Oh no!
// Error: Oh uh!"

@since2.0.0

log
(
label: string
label
,
const value: number[]
value
)
return yield*
effect: Effect.Effect<A, E, R>
effect
})
const
const task: (id: number) => Effect.Effect<void, never, never>
task
= (
id: number
id
: number) =>
const log: <never, never, void>(label: string, effect: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>
log
(
`task ${
id: number
id
}`,
import SynchronizedRef
SynchronizedRef
.
const updateEffect: <number[], never, never>(self: SynchronizedRef.SynchronizedRef<number[]>, f: (a: number[]) => Effect.Effect<number[], never, never>) => Effect.Effect<void, never, never> (+1 overload)

@since2.0.0

updateEffect
(
const ref: SynchronizedRef.SynchronizedRef<number[]>
ref
, (
sumOfAges: number[]
sumOfAges
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<number, never, never>>, number[]>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<number, never, never>>, number[], never>) => Effect.Effect<...> (+1 overload)

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.

@example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
const
const age: number
age
= yield*
const getUserAge: (userId: number) => Effect.Effect<number, never, never>
getUserAge
(
id: number
id
)
return
sumOfAges: number[]
sumOfAges
.
Array<number>.concat(...items: (number | ConcatArray<number>)[]): number[] (+1 overload)

Combines two or more arrays. This method returns a new array without modifying any existing arrays.

@paramitems Additional arrays and/or items to add to the end of the array.

concat
(
const age: number
age
)
})
)
)
// Run tasks concurrently with a limit of 2 concurrent tasks
yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const all: <readonly [Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>], {
...;
}>(arg: readonly [...], options?: {
...;
} | undefined) => Effect.Effect<...>

Combines multiple effects into one, returning results based on the input structure.

When to Use

Use Effect.all when you need to run multiple effects and combine their results into a single output. It supports tuples, iterables, structs, and records, making it flexible for different input types.

For instance, if the input is a tuple:

// ┌─── a tuple of effects
// ▼
Effect.all([effect1, effect2, ...])

the effects are executed sequentially, and the result is a new effect containing the results as a tuple. The results in the tuple match the order of the effects passed to Effect.all.

Concurrency

You can control the execution order (e.g., sequential vs. concurrent) using the concurrency option.

Short-Circuiting Behavior

The Effect.all function stops execution on the first error it encounters, this is called "short-circuiting". If any effect in the collection fails, the remaining effects will not run, and the error will be propagated. To change this behavior, you can use the mode option, which allows all effects to run and collect results as Either or Option.

The mode option

The { mode: "either" } option changes the behavior of Effect.all to ensure all effects run, even if some fail. Instead of stopping on the first failure, this mode collects both successes and failures, returning an array of Either instances where each result is either a Right (success) or a Left (failure).

Similarly, the { mode: "validate" } option uses Option to indicate success or failure. Each effect returns None for success and Some with the error for failure.

@seeforEach for iterating over elements and applying an effect.

@example

// Title: Combining Effects in Tuples
import { Effect, Console } from "effect"
const tupleOfEffects = [
Effect.succeed(42).pipe(Effect.tap(Console.log)),
Effect.succeed("Hello").pipe(Effect.tap(Console.log))
] as const
// ┌─── Effect<[number, string], never, never>
// ▼
const resultsAsTuple = Effect.all(tupleOfEffects)
Effect.runPromise(resultsAsTuple).then(console.log)
// Output:
// 42
// Hello
// [ 42, 'Hello' ]

@example

// Title: Combining Effects in Iterables import { Effect, Console } from "effect"

const iterableOfEffects: Iterable<Effect.Effect> = [1, 2, 3].map( (n) => Effect.succeed(n).pipe(Effect.tap(Console.log)) )

// ┌─── Effect<number[], never, never> // ▼ const resultsAsArray = Effect.all(iterableOfEffects)

Effect.runPromise(resultsAsArray).then(console.log) // Output: // 1 // 2 // 3 // [ 1, 2, 3 ]

@example

// Title: Combining Effects in Structs import { Effect, Console } from "effect"

const structOfEffects = { a: Effect.succeed(42).pipe(Effect.tap(Console.log)), b: Effect.succeed("Hello").pipe(Effect.tap(Console.log)) }

// ┌─── Effect<{ a: number; b: string; }, never, never> // ▼ const resultsAsStruct = Effect.all(structOfEffects)

Effect.runPromise(resultsAsStruct).then(console.log) // Output: // 42 // Hello // { a: 42, b: 'Hello' }

@example

// Title: Combining Effects in Records import { Effect, Console } from "effect"

const recordOfEffects: Record<string, Effect.Effect> = { key1: Effect.succeed(1).pipe(Effect.tap(Console.log)), key2: Effect.succeed(2).pipe(Effect.tap(Console.log)) }

// ┌─── Effect<{ [x: string]: number; }, never, never> // ▼ const resultsAsRecord = Effect.all(recordOfEffects)

Effect.runPromise(resultsAsRecord).then(console.log) // Output: // 1 // 2 // { key1: 1, key2: 2 }

@example

// Title: Short-Circuiting Behavior import { Effect, Console } from "effect"

const program = Effect.all([ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), // Won't execute due to earlier failure Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ])

Effect.runPromiseExit(program).then(console.log) // Output: // Task1 // { // _id: 'Exit', // _tag: 'Failure', // cause: { _id: 'Cause', _tag: 'Fail', failure: 'Task2: Oh no!' } // }

@example

// Title: Collecting Results with mode: "either" import { Effect, Console } from "effect"

const effects = [ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ]

const program = Effect.all(effects, { mode: "either" })

Effect.runPromiseExit(program).then(console.log) // Output: // Task1 // Task3 // { // _id: 'Exit', // _tag: 'Success', // value: [ // { _id: 'Either', _tag: 'Right', right: 'Task1' }, // { _id: 'Either', _tag: 'Left', left: 'Task2: Oh no!' }, // { _id: 'Either', _tag: 'Right', right: 'Task3' } // ] // }

@example

//Example: Collecting Results with mode: "validate" import { Effect, Console } from "effect"

const effects = [ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ]

const program = Effect.all(effects, { mode: "validate" })

Effect.runPromiseExit(program).then((result) => console.log("%o", result)) // Output: // Task1 // Task3 // { // _id: 'Exit', // _tag: 'Failure', // cause: { // _id: 'Cause', // _tag: 'Fail', // failure: [ // { _id: 'Option', _tag: 'None' }, // { _id: 'Option', _tag: 'Some', value: 'Task2: Oh no!' }, // { _id: 'Option', _tag: 'None' } // ] // } // }

@since2.0.0

all
([
const task: (id: number) => Effect.Effect<void, never, never>
task
(1),
const task: (id: number) => Effect.Effect<void, never, never>
task
(2),
const task: (id: number) => Effect.Effect<void, never, never>
task
(3),
const task: (id: number) => Effect.Effect<void, never, never>
task
(4)], {
concurrency: number
concurrency
: 2
})
// Retrieve the updated value
const
const value: number[]
value
= yield*
import SynchronizedRef
SynchronizedRef
.
const get: <number[]>(self: SynchronizedRef.SynchronizedRef<number[]>) => Effect.Effect<number[], never, never>

@since2.0.0

get
(
const ref: SynchronizedRef.SynchronizedRef<number[]>
ref
)
return
const value: number[]
value
})
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <number[], never>(effect: Effect.Effect<number[], never, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<number[]>

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.

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@example

// Title: Running a Successful Effect as a Promise
import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

@example

//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

@since2.0.0

runPromise
(
const meanAge: Effect.Effect<number[], never, never>
meanAge
).
Promise<number[]>.then<void, never>(onfulfilled?: ((value: number[]) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.

@paramonrejected The callback to execute when the Promise is rejected.

@returnsA Promise for the completion of which ever callback is executed.

then
(
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(new Error('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
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = 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(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

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()).

const count = 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.

@sincev0.1.100

log
)
/*
Output:
timestamp=... level=INFO fiber=#2 message="task 1" message=[]
timestamp=... level=INFO fiber=#3 message="task 2" message=[]
timestamp=... level=INFO fiber=#2 message="task 3" message="[
10
]"
timestamp=... level=INFO fiber=#3 message="task 4" message="[
10,
20
]"
[ 10, 20, 30, 40 ]
*/