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?
  2. 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?

  3. 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)
    6. Blocked by permissions policy: The browser or environment blocks the request due to security or permissions settings.
    7. 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!

  4. 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.Fetch.ErrorType, Fetch.Fetch>
    program
    =
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const gen: <YieldWrap<Effect.Effect<Response.Response, Fetch.Fetch.ErrorType, Fetch.Fetch>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<Response.Response, Fetch.Fetch.ErrorType, Fetch.Fetch>>, void, never>) => Effect.Effect<void, Fetch.Fetch.ErrorType, 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'
    });

    @example

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

    @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<Response.Response, Fetch.Fetch.ErrorType, 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.