Skip to content

Sandboxing

Errors are an inevitable part of programming, and they can arise from various sources like failures, defects, fiber interruptions, or combinations of these. This guide explains how to use the Effect.sandbox function to isolate and understand the causes of errors in your Effect-based code.

The Effect.sandbox function allows you to encapsulate all the potential causes of an error in an effect. It exposes the full cause of an effect, whether it’s due to a failure, defect, fiber interruption, or a combination of these factors.

In simple terms, it takes an effect Effect<A, E, R> and transforms it into an effect Effect<A, Cause<E>, R> where the error channel now contains a detailed cause of the error.

Syntax

Effect<A, E, R> -> Effect<A, Cause<E>, R>

By using the Effect.sandbox function, you gain access to the underlying causes of exceptional effects. These causes are represented as a type of Cause<E> and are available in the error channel of the Effect data type.

Once you have exposed the causes, you can utilize standard error-handling operators like Effect.catchAll and Effect.catchTags to handle errors more effectively. These operators allow you to respond to specific error conditions.

If needed, we can undo the sandboxing operation with Effect.unsandbox.

Example (Handling Different Error Causes)

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Console
Console
} from "effect"
// ┌─── Effect<string, Error, never>
// ▼
const
const task: Effect.Effect<string, Error, never>
task
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <Error>(error: Error) => Effect.Effect<never, Error, never>

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

.

@seesucceed to create an effect that represents a successful value.

@example

// Title: Creating a Failed Effect
import { Effect } from "effect"
// ┌─── Effect<never, Error, never>
// ▼
const failure = Effect.fail(
new Error("Operation failed due to network error")
)

@since2.0.0

fail
(new
var Error: ErrorConstructor
new (message?: string) => Error
Error
("Oh uh!")).
Pipeable.pipe<Effect.Effect<never, Error, never>, Effect.Effect<string, Error, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<never, Error, never>) => Effect.Effect<string, Error, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const as: <string>(value: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Replaces the value inside an effect with a constant value.

as allows you to ignore the original value inside an effect and replace it with a new constant value.

@example

// Title: Replacing a Value
import { pipe, Effect } from "effect"
// Replaces the value 5 with the constant "new value"
const program = pipe(Effect.succeed(5), Effect.as("new value"))
Effect.runPromise(program).then(console.log)
// Output: "new value"

@since2.0.0

as
("primary result")
)
// ┌─── Effect<string, Cause<Error>, never>
// ▼
const
const sandboxed: Effect.Effect<string, Cause<Error>, never>
sandboxed
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const sandbox: <string, Error, never>(self: Effect.Effect<string, Error, never>) => Effect.Effect<string, Cause<Error>, never>

The sandbox function transforms an effect by exposing the full cause of any error, defect, or fiber interruption that might occur during its execution. It changes the error channel of the effect to include detailed information about the cause, which is wrapped in a Cause<E> type.

This function is useful when you need access to the complete underlying cause of failures, defects, or interruptions, enabling more detailed error handling. Once you apply sandbox, you can use operators like

catchAll

and

catchTags

to handle specific error conditions. If necessary, you can revert the sandboxing operation with

unsandbox

to return to the original error handling behavior.

@seeunsandbox to restore the original error handling.

@example

import { Effect, Console } from "effect"
// ┌─── Effect<string, Error, never>
// ▼
const task = Effect.fail(new Error("Oh uh!")).pipe(
Effect.as("primary result")
)
// ┌─── Effect<string, Cause<Error>, never>
// ▼
const sandboxed = Effect.sandbox(task)
const program = Effect.catchTags(sandboxed, {
Die: (cause) =>
Console.log(`Caught a defect: ${cause.defect}`).pipe(
Effect.as("fallback result on defect")
),
Interrupt: (cause) =>
Console.log(`Caught a defect: ${cause.fiberId}`).pipe(
Effect.as("fallback result on fiber interruption")
),
Fail: (cause) =>
Console.log(`Caught a defect: ${cause.error}`).pipe(
Effect.as("fallback result on failure")
)
})
// Restore the original error handling with unsandbox
const main = Effect.unsandbox(program)
Effect.runPromise(main).then(console.log)
// Output:
// Caught a defect: Oh uh!
// fallback result on failure

@since2.0.0

sandbox
(
const task: Effect.Effect<string, Error, never>
task
)
const
const program: Effect.Effect<string, Empty | Sequential<Error> | Parallel<Error>, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const catchTags: <never, Cause<Error>, string, {
Die: (cause: Die) => Effect.Effect<string, never, never>;
Interrupt: (cause: Interrupt) => Effect.Effect<string, never, never>;
Fail: (cause: Fail<...>) => Effect.Effect<...>;
}>(self: Effect.Effect<...>, cases: {
Die: (cause: Die) => Effect.Effect<string, never, never>;
Interrupt: (cause: Interrupt) => Effect.Effect<string, never, never>;
Fail: (cause: Fail<...>) => Effect.Effect<...>;
}) => Effect.Effect<...> (+1 overload)

Handles multiple errors in a single block of code using their _tag field.

When to Use

catchTags is a convenient way to handle multiple error types at once. Instead of using

catchTag

multiple times, you can pass an object where each key is an error type's _tag, and the value is the handler for that specific error. This allows you to catch and recover from multiple error types in a single call.

The error type must have a readonly _tag field to use catchTag. This field is used to identify and match errors.

@example

// Title: Handling Multiple Tagged Error Types at Once
import { Effect, Random } from "effect"
class HttpError {
readonly _tag = "HttpError"
}
class ValidationError {
readonly _tag = "ValidationError"
}
// ┌─── Effect<string, HttpError | ValidationError, never>
// ▼
const program = Effect.gen(function* () {
const n1 = yield* Random.next
const n2 = yield* Random.next
if (n1 < 0.5) {
yield* Effect.fail(new HttpError())
}
if (n2 < 0.5) {
yield* Effect.fail(new ValidationError())
}
return "some result"
})
// ┌─── Effect<string, never, never>
// ▼
const recovered = program.pipe(
Effect.catchTags({
HttpError: (_HttpError) =>
Effect.succeed(`Recovering from HttpError`),
ValidationError: (_ValidationError) =>
Effect.succeed(`Recovering from ValidationError`)
})
)

@since2.0.0

catchTags
(
const sandboxed: Effect.Effect<string, Cause<Error>, never>
sandboxed
, {
type Die: (cause: Die) => Effect.Effect<string, never, never>
Die
: (
cause: Die
cause
) =>
import Console
Console
.
const log: (...args: ReadonlyArray<any>) => Effect.Effect<void>

@since2.0.0

log
(`Caught a defect: ${
cause: Die
cause
.
Die.defect: unknown
defect
}`).
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<string, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const as: <string>(value: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Replaces the value inside an effect with a constant value.

as allows you to ignore the original value inside an effect and replace it with a new constant value.

@example

// Title: Replacing a Value
import { pipe, Effect } from "effect"
// Replaces the value 5 with the constant "new value"
const program = pipe(Effect.succeed(5), Effect.as("new value"))
Effect.runPromise(program).then(console.log)
// Output: "new value"

@since2.0.0

as
("fallback result on defect")
),
type Interrupt: (cause: Interrupt) => Effect.Effect<string, never, never>
Interrupt
: (
cause: Interrupt
cause
) =>
import Console
Console
.
const log: (...args: ReadonlyArray<any>) => Effect.Effect<void>

@since2.0.0

log
(`Caught a defect: ${
cause: Interrupt
cause
.
Interrupt.fiberId: FiberId
fiberId
}`).
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<string, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const as: <string>(value: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Replaces the value inside an effect with a constant value.

as allows you to ignore the original value inside an effect and replace it with a new constant value.

@example

// Title: Replacing a Value
import { pipe, Effect } from "effect"
// Replaces the value 5 with the constant "new value"
const program = pipe(Effect.succeed(5), Effect.as("new value"))
Effect.runPromise(program).then(console.log)
// Output: "new value"

@since2.0.0

as
("fallback result on fiber interruption")
),
type Fail: (cause: Fail<Error>) => Effect.Effect<string, never, never>
Fail
: (
cause: Fail<Error>
cause
) =>
import Console
Console
.
const log: (...args: ReadonlyArray<any>) => Effect.Effect<void>

@since2.0.0

log
(`Caught a defect: ${
cause: Fail<Error>
cause
.
Fail<Error>.error: Error
error
}`).
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<string, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const as: <string>(value: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<string, E, R> (+1 overload)

Replaces the value inside an effect with a constant value.

as allows you to ignore the original value inside an effect and replace it with a new constant value.

@example

// Title: Replacing a Value
import { pipe, Effect } from "effect"
// Replaces the value 5 with the constant "new value"
const program = pipe(Effect.succeed(5), Effect.as("new value"))
Effect.runPromise(program).then(console.log)
// Output: "new value"

@since2.0.0

as
("fallback result on failure")
)
})
// Restore the original error handling with unsandbox
const
const main: Effect.Effect<string, Error, never>
main
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const unsandbox: <string, Error, never>(self: Effect.Effect<string, Cause<Error>, never>) => Effect.Effect<string, Error, never>

The unsandbox function is used to revert an effect that has been sandboxed by

sandbox

. When you apply unsandbox, the effect's error channel is restored to its original state, without the detailed Cause<E> information. This means that any underlying causes of errors, defects, or fiber interruptions are no longer exposed in the error channel.

This function is useful when you want to remove the detailed error tracking provided by sandbox and return to the standard error handling for your effect. Once unsandboxed, the effect behaves as if sandbox was never applied.

@seesandbox to expose the full cause of failures, defects, or interruptions.

@since2.0.0

unsandbox
(
const program: Effect.Effect<string, Empty | Sequential<Error> | Parallel<Error>, never>
program
)
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

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 main: Effect.Effect<string, Error, never>
main
).
Promise<string>.then<void, never>(onfulfilled?: ((value: string) => 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
.
globalThis.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:
Caught a defect: Oh uh!
fallback result on failure
*/