Skip to content

Why FxFetch?

If you’re using EffectTS to build your applications and make HTTP requests, you’ll find FxFetch to be a perfect fit. It provides a type-safe, ergonomic API for handling fetch operations.

There are two main reasons:

  1. Non-trivial error handling
  2. Lack of immutability
  1. Let’s start with an example
const
const program: Effect.Effect<void, MyError, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<Response, MyError, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<Response, MyError, never>>, void, never>) => Effect.Effect<void, MyError, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
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 response: Response
response
= yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Response, MyError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Response>;
readonly catch: (error: unknown) => MyError;
}) => Effect.Effect<Response, MyError, never> (+1 overload)

Creates an Effect that represents an asynchronous computation that might fail.

When to Use

In situations where you need to perform asynchronous operations that might fail, such as fetching data from an API, you can use the tryPromise constructor. This constructor is designed to handle operations that could throw exceptions by capturing those exceptions and transforming them into manageable errors.

Error Handling

There are two ways to handle errors with tryPromise:

  1. If you don't provide a catch function, the error is caught and the effect fails with an UnknownException.
  2. If you provide a catch function, the error is caught and the catch function maps it to an error of type E.

Interruptions

An optional AbortSignal can be provided to allow for interruption of the wrapped Promise API.

Example (Fetching a TODO Item)

import { Effect } from "effect"
const getTodo = (id: number) =>
// Will catch any errors and propagate them as UnknownException
Effect.tryPromise(() =>
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
)
// ┌─── Effect<Response, UnknownException, never>
// ▼
const program = getTodo(1)

Example (Custom Error Handling)

import { Effect } from "effect"
const getTodo = (id: number) =>
Effect.tryPromise({
try: () => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`),
// remap the error
catch: (unknown) => new Error(`something went wrong ${unknown}`)
})
// ┌─── Effect<Response, Error, never>
// ▼
const program = getTodo(1)

@seepromise if the effectful computation is asynchronous and does not throw errors.

@since2.0.0

tryPromise
({
try: (signal: AbortSignal) => PromiseLike<Response>
try
: () =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)

Send a HTTP(s) request

@paraminput URL string or Request object

@paraminit A structured value that contains settings for the fetch() request.

@returnsA promise that resolves to Response object.

fetch
('./my-endpoint'),
catch: (error: unknown) => MyError
catch
: () => new
constructor MyError<{}>(args: void): MyError

A custom error type for demonstration purposes.

MyError
(),
});
function processResponse(response: Response): unknown

Some logic to process the response. Just a placeholder.

processResponse
(
const response: Response
response
); // Some logic here
});

Do you see the problem here? There’s more than meets the eye.

  • Missing promise cancellation
  • Is the response OK? What if it’s a 4XX or 5XX?
  • Why does it fail?
  1. Let’s improve it a bit
// ┌─── Effect.Effect<void, MyError, never>
// ▼
const
const program: Effect.Effect<void, MyError, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<Response, MyError, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<Response, MyError, never>>, void, never>) => Effect.Effect<void, MyError, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
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 response: Response
response
= yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Response, MyError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Response>;
readonly catch: (error: unknown) => MyError;
}) => Effect.Effect<Response, MyError, never> (+1 overload)

Creates an Effect that represents an asynchronous computation that might fail.

When to Use

In situations where you need to perform asynchronous operations that might fail, such as fetching data from an API, you can use the tryPromise constructor. This constructor is designed to handle operations that could throw exceptions by capturing those exceptions and transforming them into manageable errors.

Error Handling

There are two ways to handle errors with tryPromise:

  1. If you don't provide a catch function, the error is caught and the effect fails with an UnknownException.
  2. If you provide a catch function, the error is caught and the catch function maps it to an error of type E.

Interruptions

An optional AbortSignal can be provided to allow for interruption of the wrapped Promise API.

Example (Fetching a TODO Item)

import { Effect } from "effect"
const getTodo = (id: number) =>
// Will catch any errors and propagate them as UnknownException
Effect.tryPromise(() =>
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
)
// ┌─── Effect<Response, UnknownException, never>
// ▼
const program = getTodo(1)

Example (Custom Error Handling)

import { Effect } from "effect"
const getTodo = (id: number) =>
Effect.tryPromise({
try: () => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`),
// remap the error
catch: (unknown) => new Error(`something went wrong ${unknown}`)
})
// ┌─── Effect<Response, Error, never>
// ▼
const program = getTodo(1)

@seepromise if the effectful computation is asynchronous and does not throw errors.

@since2.0.0

tryPromise
({
try: (signal: AbortSignal) => PromiseLike<Response>
try
: (
signal: AbortSignal
signal
) =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)

Send a HTTP(s) request

@paraminput URL string or Request object

@paraminit A structured value that contains settings for the fetch() request.

@returnsA promise that resolves to Response object.

fetch
('./my-endpoint', {
signal?: AbortSignal | null
signal
}),
catch: (error: unknown) => MyError
catch
: () => new
constructor MyError<{}>(args: void): MyError

A custom error type for demonstration purposes.

MyError
(),
}).
Pipeable.pipe<Effect.Effect<Response, MyError, never>, Effect.Effect<Response, MyError, never>>(this: Effect.Effect<Response, MyError, never>, ab: (_: Effect.Effect<Response, MyError, never>) => Effect.Effect<Response, MyError, never>): Effect.Effect<Response, MyError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <Response, Response, MyError, never>(f: (a: Response) => Effect.Effect<Response, MyError, never>) => <E, R>(self: Effect.Effect<Response, E, R>) => Effect.Effect<Response, MyError | E, R> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
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)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
((
response: Response
response
) => {
if (!
response: Response
response
.
Response.ok: boolean
ok
) {
return
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <MyError>(error: MyError) => Effect.Effect<never, MyError, 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

.

Example (Creating a Failed Effect)

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

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

@since2.0.0

fail
(new
constructor MyError<{}>(args: void): MyError

A custom error type for demonstration purposes.

MyError
());
}
return
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <Response>(value: Response) => Effect.Effect<Response, 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.

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

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
(
response: Response
response
);
})
);
function processResponse(response: Response): unknown

Some logic to process the response. Just a placeholder.

processResponse
(
const response: Response
response
); // Some logic here
});
  • Missing promise cancellation
  • Is the response OK? What if it’s a 4XX or 5XX?
  • Why does it fail?

This is safer, but still not useful enough. What does MyError mean? Can I retry?

  1. Let’s improve the error handling

What are the possible reasons for failure?

Here are the most common reasons why a fetch request might fail:

  1. Non-OK HTTP response: The server responded, but with a status code outside the 200–299 range.
  2. Request aborted: The request was cancelled via an AbortController.
  3. Network error: The network is unreachable, DNS fails, or the connection is lost.
  4. CORS not allowed: The request is blocked due to Cross-Origin Resource Sharing (CORS) policy.
  5. Invalid request parameters:
  • Invalid header name or value
  • Invalid URL or unsupported protocol/scheme
  • URL includes unwanted credentials
  • Invalid referrer URL
  • Invalid request mode (e.g., navigate, websocket)
  • Forbidden HTTP method (e.g., CONNECT, TRACE, TRACK)
  • Body present with GET or HEAD method
  • Cache mode or request mode mismatch (e.g., only-if-cached with non-same-origin)
  1. Blocked by permissions policy: The browser or environment blocks the request due to security or permissions settings.
  2. Out of memory: The environment cannot allocate enough memory to complete the request (rare).

That’s pretty much it. Let’s model these errors.

// ┌─── Effect.Effect<
// │ void,
// │ | FetchError
// │ | AbortError
// │ | NotAllowedError
// │ | NotOkError,
// │ never
// │ >
// ▼
const
const program: Effect.Effect<void, FetchError | AbortError | NotAllowedError | NotOkError, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<Response, FetchError | AbortError | NotAllowedError | NotOkError, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<Response, FetchError | AbortError | NotAllowedError | NotOkError, never>>, void, never>) => Effect.Effect<void, FetchError | AbortError | NotAllowedError | NotOkError, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
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 response: Response
response
= yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Response, FetchError | AbortError | NotAllowedError | UnknownError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Response>;
readonly catch: (error: unknown) => FetchError | AbortError | NotAllowedError | UnknownError;
}) => Effect.Effect<Response, FetchError | AbortError | NotAllowedError | UnknownError, never> (+1 overload)

Creates an Effect that represents an asynchronous computation that might fail.

When to Use

In situations where you need to perform asynchronous operations that might fail, such as fetching data from an API, you can use the tryPromise constructor. This constructor is designed to handle operations that could throw exceptions by capturing those exceptions and transforming them into manageable errors.

Error Handling

There are two ways to handle errors with tryPromise:

  1. If you don't provide a catch function, the error is caught and the effect fails with an UnknownException.
  2. If you provide a catch function, the error is caught and the catch function maps it to an error of type E.

Interruptions

An optional AbortSignal can be provided to allow for interruption of the wrapped Promise API.

Example (Fetching a TODO Item)

import { Effect } from "effect"
const getTodo = (id: number) =>
// Will catch any errors and propagate them as UnknownException
Effect.tryPromise(() =>
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
)
// ┌─── Effect<Response, UnknownException, never>
// ▼
const program = getTodo(1)

Example (Custom Error Handling)

import { Effect } from "effect"
const getTodo = (id: number) =>
Effect.tryPromise({
try: () => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`),
// remap the error
catch: (unknown) => new Error(`something went wrong ${unknown}`)
})
// ┌─── Effect<Response, Error, never>
// ▼
const program = getTodo(1)

@seepromise if the effectful computation is asynchronous and does not throw errors.

@since2.0.0

tryPromise
({
try: (signal: AbortSignal) => PromiseLike<Response>
try
: (
signal: AbortSignal
signal
) =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)

Send a HTTP(s) request

@paraminput URL string or Request object

@paraminit A structured value that contains settings for the fetch() request.

@returnsA promise that resolves to Response object.

fetch
('./my-endpoint', {
signal?: AbortSignal | null
signal
}),
catch: (error: unknown) => FetchError | AbortError | NotAllowedError | UnknownError
catch
(
error: unknown
error
) {
if (
error: unknown
error
instanceof
var TypeError: TypeErrorConstructor
TypeError
) {
return new
constructor FetchError<{}>(args: void): FetchError

Wrapper for TypeError. It is almost impossible to categorize all possible errors, as it depends on the environment (browser, Node.js, Deno, etc.), location, network, and more.

Possible reasons:

  • Blocked by a permissions policy.
  • Invalid header name.
  • Invalid header value. The header object must contain exactly two elements.
  • Invalid URL or scheme, or using a scheme that fetch does not support, or using a scheme that is not supported for a particular request mode.
  • URL includes credentials.
  • Invalid referrer URL.
  • Invalid modes (navigate and websocket).
  • If the request cache mode is "only-if-cached" and the request mode is other than "same-origin".
  • If the request method is an invalid name token or one of the forbidden headers ('CONNECT', 'TRACE' or 'TRACK').
  • If the request mode is "no-cors" and the request method is not a CORS-safe-listed method ('GET', 'HEAD', or 'POST').
  • If the request method is 'GET' or 'HEAD' and the body is non-null or not undefined.
  • If fetch throws a network error.

FetchError
();
}
if (typeof
error: unknown
error
=== 'object' &&
error: object | null
error
!== null && 'name' in
error: object
error
) {
if (
error: object & Record<"name", unknown>
error
.
name: unknown
name
=== 'AbortError') {
return new
constructor AbortError<{}>(args: void): AbortError

The request was aborted due to a call to the AbortController abort() method.

AbortError
();
}
if (
error: object & Record<"name", unknown>
error
.
name: unknown
name
=== 'NotAllowedError') {
return new
constructor NotAllowedError<{}>(args: void): NotAllowedError

Thrown if use of the Topics API is specifically disallowed by a browsing-topics Permissions Policy, and a fetch() request was made with browsingTopics: true.

NotAllowedError
();
}
}
// Fallback for Out-of-Memory and other unknown errors
return new
constructor UnknownError<{}>(args: void): UnknownError

An unknown error occurred during the fetch request.

UnknownError
();
},
}).
Pipeable.pipe<Effect.Effect<Response, FetchError | AbortError | NotAllowedError | UnknownError, never>, Effect.Effect<Response, FetchError | AbortError | NotAllowedError, never>, Effect.Effect<Response, FetchError | AbortError | NotAllowedError | NotOkError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Response, FetchError | AbortError | NotAllowedError | UnknownError, never>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const catchTag: <FetchError | AbortError | NotAllowedError | UnknownError, ["UnknownError"], never, never, never>(args_0: "UnknownError", f: (e: UnknownError) => Effect.Effect<never, never, never>) => <A, R>(self: Effect.Effect<A, FetchError | AbortError | NotAllowedError | UnknownError, R>) => Effect.Effect<A, FetchError | AbortError | NotAllowedError, R> (+1 overload)

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.

Example (Handling Errors by Tag)

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, ValidationError, never>
// ▼
const recovered = program.pipe(
// Only handle HttpError errors
Effect.catchTag("HttpError", (_HttpError) =>
Effect.succeed("Recovering from HttpError")
)
)

@seecatchTags for a version that allows you to handle multiple error types at once.

@since2.0.0

catchTag
('UnknownError', () =>
// Out-of-Memory error is mostly defect. So we can die here.
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const dieMessage: (message: string) => Effect.Effect<never>

Creates an effect that terminates a fiber with a RuntimeException containing the specified message.

Details

This function is used to signal a defect, representing a critical and unexpected error in the code. When invoked, it produces an effect that terminates the fiber with a RuntimeException carrying the given message.

The resulting effect has an error channel of type never, indicating it does not handle or recover from the error.

When to Use

Use this function when you want to terminate a fiber due to an unrecoverable defect and include a clear explanation in the message.

Example (Terminating on Division by Zero with a Specified Message)

import { Effect } from "effect"
const divide = (a: number, b: number) =>
b === 0
? Effect.dieMessage("Cannot divide by zero")
: Effect.succeed(a / b)
// ┌─── Effect<number, never, never>
// ▼
const program = divide(1, 0)
Effect.runPromise(program).catch(console.error)
// Output:
// (FiberFailure) RuntimeException: Cannot divide by zero
// ...stack trace...

@seedie for a variant that throws a specified error.

@seedieSync for a variant that throws a specified error, evaluated lazily.

@since2.0.0

dieMessage
('Unknown error occurred during fetch request')
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <Response, Response, NotOkError, never>(f: (a: Response) => Effect.Effect<Response, NotOkError, never>) => <E, R>(self: Effect.Effect<Response, E, R>) => Effect.Effect<Response, NotOkError | E, R> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
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)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
((
response: Response
response
) => {
if (!
response: Response
response
.
Response.ok: boolean
ok
) {
return
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <NotOkError>(error: NotOkError) => Effect.Effect<never, NotOkError, 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

.

Example (Creating a Failed Effect)

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

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

@since2.0.0

fail
(new
constructor NotOkError<{}>(args: void): NotOkError

Thrown if the response is not OK. So the status code is not in the range 200-299.

NotOkError
());
}
return
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <Response>(value: Response) => Effect.Effect<Response, 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.

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

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
(
response: Response
response
);
})
);
function processResponse(response: Response): unknown

Some logic to process the response. Just a placeholder.

processResponse
(
const response: Response
response
); // Some logic here
});
  • Missing promise cancellation
  • Is the response OK? What if it’s a 4XX or 5XX?
  • Why does it fail?

Nice! We did it. It is perfect, right? Nope.

Moving from a non-effect world to an effect world can be tedious, verbose and error-prone. It’s similar to using untyped JavaScript in TypeScript. It’s better if the hard work is done by someone else.

In the untyped npm ecosystem, we have the @types/* initiative. In this case, we have FxFetch!

  1. Use FxFetch to do the heavy lifting for you 🎉
import {
import Fetch
Fetch
,
import Request
Request
,
import Response
Response
} from 'fx-fetch';
// ┌─── Effect.Effect<
// │ void,
// │ | Fetch.FetchError
// │ | Fetch.AbortError
// │ | Fetch.NotAllowedError
// │ | Response.NotOkError,
// │ Fetch.Fetch
// │ >
// ▼
const
const program: Effect.Effect<void, Fetch.FetchError | Fetch.AbortError | Fetch.NotAllowedError | Response.NotOkError, Fetch.Fetch>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<Response.Response, Fetch.FetchError | Fetch.AbortError | Fetch.NotAllowedError | Response.NotOkError, Fetch.Fetch>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<Response.Response, Fetch.FetchError | Fetch.AbortError | Fetch.NotAllowedError | Response.NotOkError, Fetch.Fetch>>, void, never>) => Effect.Effect<void, Fetch.FetchError | ... 2 more ... | Response.NotOkError, Fetch.Fetch> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
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 request: Request.Request
request
=
import Request
Request
.
function unsafeMake(input: Request.Request.Input): Request.Request
export unsafeMake

Creates a immutable Request object. Throws an error if the input is invalid.

@example

import { Request } from 'fx-fetch';
// ┌─── Request.Request
// ▼
const request = Request.unsafeMake({
url: 'https://example.com',
method: 'POST'
});

@since0.1.0

unsafeMake
({
url: string
url
: './my-endpoint' });
const
const response: Response.Response
response
= yield*
import Fetch
Fetch
.
function fetch(request: Request.Request): Effect.Effect<import("/home/runner/work/fx-fetch/fx-fetch/packages/fx-fetch/dist/Response/index").Response, import("/home/runner/work/fx-fetch/fx-fetch/packages/fx-fetch/dist/Fetch/errors").FetchError | import("/home/runner/work/fx-fetch/fx-fetch/packages/fx-fetch/dist/Fetch/errors").AbortError | import("/home/runner/work/fx-fetch/fx-fetch/packages/fx-fetch/dist/Fetch/errors").NotAllowedError | import("/home/runner/work/fx-fetch/fx-fetch/packages/fx-fetch/dist/Response/index").NotOkError, Fetch.Fetch>
export fetch

@since0.1.0

@example

import { Effect } from 'effect';
import { Fetch, Request, Response } from 'fx-fetch';
// ┌─── Effect.Effect<
// │ void,
// │ | Fetch.FetchError
// │ | Fetch.AbortError
// │ | Fetch.NotAllowedError
// │ | Response.NotOkError,
// │ Fetch.Fetch
// │ >
// ▼
const program = Effect.gen(function* () {
const request = Request.unsafeMake({ url: './my-endpoint' });
// ┌─── Response.Response
// ▼
const response = yield* Fetch.fetch(request);
});
// Run the program
program.pipe(
Effect.provideService(Fetch.Fetch, Fetch.FetchLive),
Effect.runFork // or Effect.runPromise etc.
);

fetch
(
const request: Request.Request
request
);
function processResponse(response: Response.Response): unknown

Some logic to process the response. Just a placeholder.

processResponse
(
const response: Response.Response
response
); // Some logic here
});

EffectTS is built on functional programming principles. All building blocks (Effect, Option, Either, DateTime, …) are immutable and clonable by design.

For a library to truly be in symbiosis with EffectTS, it must adhere to the same principles.

EffectTS excels at parallel actions and concurrency. Without using immutable and clonable structures, unexpected issues may appear.

Working with immutable objects can be challenging without proper tools — that’s why we built fx-fetch.

Real-world examples of why immutability and clonability matter:

  • Reusing the same Request for paginated API calls
  • Appending general headers to already-created Request objects
  • Retrying failed requests without worrying about side effects

Even when they work correctly, they don’t solve DX issues or provide key features like reading request/response properties multiple times without side effects.

const
const req: Request
req
= new
module globalThis
globalThis
.
var Request: new (requestInfo: string, init?: RequestInit) => Request (+2 overloads)
Request
("./endpoint", {
method?: string
method
: "POST",
body?: Bun.BodyInit | null | undefined
body
: "Hello World", // ◀︎── string
});
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 (+1 overload)

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
(
const req: Request
req
.
BodyMixin.body: ReadableStream<any> | null
body
); // `ReadableStream { }`
// ReadableStream can only be read once.
// You must know what you're reading.