Skip to content

批处理

在典型的应用程序开发中,当与外部API、数据库或其他数据源交互时,我们经常定义执行请求并相应处理其结果或失败的函数。

这是一个概述我们数据结构和可能错误的基本模型:

import {
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}

让我们定义与外部API交互的函数,处理常见操作,如获取待办事项、检索用户详细信息和发送电子邮件。

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// API
// ------------------------------
// 从外部API获取待办事项列表
const
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Todo[], GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<Todo[], GetTodosError, 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<Todo[]>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<Todo[]>

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
(
(
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
// 通过ID从外部API检索用户
const
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
= (
id: number
id
: number) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <User, GetUserError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<User>;
readonly catch: (error: unknown) => GetUserError;
}) => Effect.Effect<User, GetUserError, 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<User>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
(`https://api.example.demo/getUserById?id=${
id: number
id
}`).
Promise<Response>.then<User, never>(onfulfilled?: ((value: Response) => User | PromiseLike<User>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<User>

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
(
(
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface User
User
>
),
catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError<{}>(args: void): GetUserError
GetUserError
()
})
// 通过外部API发送电子邮件
const
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
= (
address: string
address
: string,
text: string
text
: string) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <void, SendEmailError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<void>;
readonly catch: (error: unknown) => SendEmailError;
}) => Effect.Effect<void, SendEmailError, 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<void>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmail", {
RequestInit.method?: string
method
: "POST",
RequestInit.headers?: HeadersInit
headers
: {
"Content-Type": "application/json"
},
RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
address: string
address
,
text: string
text
})
}).
Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError<{}>(args: void): SendEmailError
SendEmailError
()
})
// 通过首先获取用户详细信息向用户发送电子邮件
const
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
= (
id: number
id
: number,
message: string
message
: string) =>
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
id: number
id
).
Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<User, GetUserError, never>, ab: (_: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, GetUserError | SendEmailError, never>): Effect.Effect<void, GetUserError | SendEmailError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User, Effect.Effect<void, SendEmailError, never>>(f: (a: User) => Effect.Effect<void, SendEmailError, never>) => <E, R>(self: Effect.Effect<User, E, R>) => Effect.Effect<void, SendEmailError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
user: User
user
) =>
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
(
user: User
user
.
User.email: string
email
,
message: string
message
))
)
// 通过发送电子邮件通知待办事项的所有者
const
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
= (
todo: Todo
todo
:
interface Todo
Todo
) =>
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
todo: Todo
todo
.
Todo.ownerId: number
ownerId
).
Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<User, GetUserError, never>, ab: (_: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, GetUserError | SendEmailError, never>): Effect.Effect<void, GetUserError | SendEmailError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User, Effect.Effect<void, GetUserError | SendEmailError, never>>(f: (a: User) => Effect.Effect<void, GetUserError | SendEmailError, never>) => <E, R>(self: Effect.Effect<User, E, R>) => Effect.Effect<void, GetUserError | SendEmailError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
user: User
user
) =>
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
(
user: User
user
.
User.id: number
id
, `hey ${
user: User
user
.
User.name: string
name
} you got a todo!`)
)
)

虽然这种方法简单易读,但可能不是最高效的。重复的API调用,特别是当许多待办事项共享同一个所有者时,可能会显著增加网络开销并减慢应用程序的速度。

虽然这些函数清晰易懂,但它们的使用可能不是最高效的。例如,通知待办事项所有者涉及重复的API调用,这可以进行优化。

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// API
// ------------------------------
46 collapsed lines
// Fetches a list of todos from an external API
const
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Todo[], GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<Todo[], GetTodosError, 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<Todo[]>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<Todo[]>

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
(
(
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
// Retrieves a user by their ID from an external API
const
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
= (
id: number
id
: number) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <User, GetUserError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<User>;
readonly catch: (error: unknown) => GetUserError;
}) => Effect.Effect<User, GetUserError, 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<User>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
(`https://api.example.demo/getUserById?id=${
id: number
id
}`).
Promise<Response>.then<User, never>(onfulfilled?: ((value: Response) => User | PromiseLike<User>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<User>

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
(
(
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface User
User
>
),
catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError<{}>(args: void): GetUserError
GetUserError
()
})
// Sends an email via an external API
const
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
= (
address: string
address
: string,
text: string
text
: string) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <void, SendEmailError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<void>;
readonly catch: (error: unknown) => SendEmailError;
}) => Effect.Effect<void, SendEmailError, 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<void>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmail", {
RequestInit.method?: string
method
: "POST",
RequestInit.headers?: HeadersInit
headers
: {
"Content-Type": "application/json"
},
RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
address: string
address
,
text: string
text
})
}).
Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError<{}>(args: void): SendEmailError
SendEmailError
()
})
// Sends an email to a user by fetching their details first
const
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
= (
id: number
id
: number,
message: string
message
: string) =>
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
id: number
id
).
Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<User, GetUserError, never>, ab: (_: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, GetUserError | SendEmailError, never>): Effect.Effect<void, GetUserError | SendEmailError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User, Effect.Effect<void, SendEmailError, never>>(f: (a: User) => Effect.Effect<void, SendEmailError, never>) => <E, R>(self: Effect.Effect<User, E, R>) => Effect.Effect<void, SendEmailError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
user: User
user
) =>
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
(
user: User
user
.
User.email: string
email
,
message: string
message
))
)
// Notifies the owner of a todo by sending them an email
const
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
= (
todo: Todo
todo
:
interface Todo
Todo
) =>
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
todo: Todo
todo
.
Todo.ownerId: number
ownerId
).
Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<User, GetUserError, never>, ab: (_: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, GetUserError | SendEmailError, never>): Effect.Effect<void, GetUserError | SendEmailError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User, Effect.Effect<void, GetUserError | SendEmailError, never>>(f: (a: User) => Effect.Effect<void, GetUserError | SendEmailError, never>) => <E, R>(self: Effect.Effect<User, E, R>) => Effect.Effect<void, GetUserError | SendEmailError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
user: User
user
) =>
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
(
user: User
user
.
User.id: number
id
, `hey ${
user: User
user
.
User.name: string
name
} you got a todo!`)
)
)
// 协调待办事项操作,通知其所有者
// 此程序将进行 1 + 2n 次API调用
// 其中n是待办事项的数量
// 1次调用获取待办事项 + 每个待办事项2次调用(获取用户 + 发送电子邮件)
const
const program: Effect.Effect<void, GetUserError | GetTodosError | SendEmailError, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<Todo[], GetTodosError, never>> | YieldWrap<Effect.Effect<void[], GetUserError | SendEmailError, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<Todo[], GetTodosError, never>> | YieldWrap<Effect.Effect<void[], GetUserError | SendEmailError, never>>, void, never>) => Effect.Effect<...> (+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 todos: Todo[]
todos
= yield*
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, GetUserError | SendEmailError, never, Todo[]>(self: Todo[], f: (a: Todo, i: number) => Effect.Effect<void, GetUserError | SendEmailError, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<...> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
const todos: Todo[]
todos
, (
todo: Todo
todo
) =>
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
(
todo: Todo
todo
), {
concurrency?: Concurrency | undefined
concurrency
: "unbounded"
})
})

此实现为每个待办事项执行API调用以获取所有者的详细信息并发送电子邮件。如果多个待办事项具有相同的所有者,这会导致冗余的API调用。

让我们假设getUserByIdsendEmail可以进行批处理。这意味着我们可以在单个HTTP调用中发送多个请求,减少API请求的数量并提高性能。

批处理分步指南

  1. 声明请求: 我们将首先将请求转换为结构化数据模型。这涉及详细说明输入参数、预期输出和可能的错误。以这种方式构造请求不仅有助于高效管理数据,还有助于比较不同的请求以了解它们是否引用相同的输入参数。

  2. 声明解析器: 解析器旨在同时处理多个请求。通过利用比较请求的能力(确保它们引用相同的输入参数),解析器可以一次执行多个请求,最大化批处理的效用。

  3. 定义查询: 最后,我们将定义利用这些批处理解析器执行操作的查询。此步骤将结构化请求及其相应的解析器绑定到应用程序的功能组件中。

我们将使用数据源可能支持的Request概念来设计模型:

Request<Value, Error>

一个Request是表示对类型为Value的值的请求的构造,该请求可能因类型为Error的错误而失败。

让我们首先为数据源可以处理的请求类型定义一个结构化模型。

import {
import Request
Request
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// Requests
// ------------------------------
// 定义一个获取多个Todo项目的请求,可能
// 因GetTodosError而失败
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
readonly
GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
}
// 为GetTodos请求创建标记构造函数
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
// 定义一个通过ID获取用户的请求,可能
// 因GetUserError而失败
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
readonly
GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
readonly
GetUserById.id: number
id
: number
}
// 为GetUserById请求创建标记构造函数
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
// 定义一个发送电子邮件的请求,可能
// 因SendEmailError而失败
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<void,
class SendEmailError
SendEmailError
> {
readonly
SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
readonly
SendEmail.address: string
address
: string
readonly
SendEmail.text: string
text
: string
}
// 为SendEmail请求创建标记构造函数
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface SendEmail
SendEmail
>("SendEmail")

Each request is defined with a specific data structure that extends from a generic Request type, ensuring that each request carries its unique data requirements along with a specific error type.

By using tagged constructors like Request.tagged, we can easily instantiate request objects that are recognizable and manageable throughout the application.

定义请求后,下一步是配置Effect如何使用RequestResolver解析这些请求:

RequestResolver<A, R>

RequestResolver需要环境R并且能够执行类型为A的请求。

在本节中,我们将为每种类型的请求创建单独的解析器。解析器的粒度可以变化,但通常,它们根据相应API调用的批处理能力进行划分。

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Request
Request
,
import RequestResolver
RequestResolver
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// Requests
// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might
// fail with a GetTodosError
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
readonly
GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
}
// Create a tagged constructor for GetTodos requests
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
// Define a request to fetch a User by ID which might
// fail with a GetUserError
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
readonly
GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
readonly
GetUserById.id: number
id
: number
}
// Create a tagged constructor for GetUserById requests
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
// Define a request to send an email which might
// fail with a SendEmailError
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<void,
class SendEmailError
SendEmailError
> {
readonly
SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
readonly
SendEmail.address: string
address
: string
readonly
SendEmail.text: string
text
: string
}
// Create a tagged constructor for SendEmail requests
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
// ------------------------------
// Resolvers
// ------------------------------
// 假设GetTodos无法批处理,我们创建一个标准解析器
const
const GetTodosResolver: RequestResolver.RequestResolver<GetTodos, never>
GetTodosResolver
=
import RequestResolver
RequestResolver
.
const fromEffect: <never, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, never>) => RequestResolver.RequestResolver<GetTodos, never>

Constructs a data source from an effectual function.

@since2.0.0

fromEffect
(
(
_: GetTodos
_
:
interface GetTodos
GetTodos
):
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
interface Todo
Todo
[],
class GetTodosError
GetTodosError
> =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Todo[], GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<Todo[], GetTodosError, 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<Todo[]>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
Promise<Response>.then<Todo[], Todo[]>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => Todo[] | PromiseLike<Todo[]>) | null | undefined): Promise<Todo[]>

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
(
(
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
)
// 假设GetUserById可以批处理,我们创建一个批处理解析器
const
const GetUserByIdResolver: RequestResolver.RequestResolver<GetUserById, never>
GetUserByIdResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <GetUserById, never>(run: (requests: [GetUserById, ...GetUserById[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<GetUserById, never>

Constructs a data source from a function taking a collection of requests.

@since2.0.0

makeBatched
(
(
requests: readonly GetUserById[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface GetUserById
GetUserById
>) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <User[], GetUserError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<User[]>;
readonly catch: (error: unknown) => GetUserError;
}) => Effect.Effect<User[], GetUserError, 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<User[]>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/getUserByIdBatch", {
RequestInit.method?: string
method
: "POST",
RequestInit.headers?: HeadersInit
headers
: {
"Content-Type": "application/json"
},
RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
users: {
id: number;
}[]
users
:
requests: readonly GetUserById[]
requests
.
ReadonlyArray<GetUserById>.map<{
id: number;
}>(callbackfn: (value: GetUserById, index: number, array: readonly GetUserById[]) => {
id: number;
}, thisArg?: any): {
id: number;
}[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
(({
id: number
id
}) => ({
id: number
id
}))
})
}).
Promise<Response>.then<unknown, User[]>(onfulfilled?: ((value: Response) => unknown) | null | undefined, onrejected?: ((reason: any) => User[] | PromiseLike<User[]>) | null | undefined): Promise<unknown>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
()) as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface User
User
>>,
catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError<{}>(args: void): GetUserError
GetUserError
()
}).
Pipeable.pipe<Effect.Effect<User[], GetUserError, never>, Effect.Effect<void[], GetUserError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<User[], GetUserError, never>, ab: (_: Effect.Effect<User[], GetUserError, never>) => Effect.Effect<void[], GetUserError, never>, bc: (_: Effect.Effect<void[], GetUserError, never>) => Effect.Effect<void[], never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User[], Effect.Effect<void[], never, never>>(f: (a: User[]) => Effect.Effect<void[], never, never>) => <E, R>(self: Effect.Effect<User[], E, R>) => Effect.Effect<void[], E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
users: User[]
users
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly GetUserById[]
requests
, (
request: GetUserById
request
,
index: number
index
) =>
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: GetUserById
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <User>(value: User) => Effect.Effect<User, 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
(
users: User[]
users
[
index: number
index
]!))
)
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const catchAll: <GetUserError, void[], never, never>(f: (e: GetUserError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, GetUserError, R>) => Effect.Effect<void[] | A, never, R> (+1 overload)

Handles all errors in an effect by providing a fallback effect.

Details

This function catches any errors that may occur during the execution of an effect and allows you to handle them by specifying a fallback effect. This ensures that the program continues without failing by recovering from errors using the provided fallback logic.

Note: This function only handles recoverable errors. It will not recover from unrecoverable defects.

Example (Providing Recovery Logic for Recoverable Errors)

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.catchAll((error) =>
Effect.succeed(`Recovering from ${error._tag}`)
)
)

@seecatchAllCause for a version that can recover from both recoverable and unrecoverable errors.

@since2.0.0

catchAll
((
error: GetUserError
error
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly GetUserById[]
requests
, (
request: GetUserById
request
) =>
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: GetUserById
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <GetUserError>(error: GetUserError) => Effect.Effect<never, GetUserError, 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
(
error: GetUserError
error
))
)
)
)
)
// 假设SendEmail可以批处理,我们创建一个批处理解析器
const
const SendEmailResolver: RequestResolver.RequestResolver<SendEmail, never>
SendEmailResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <SendEmail, never>(run: (requests: [SendEmail, ...SendEmail[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<SendEmail, never>

Constructs a data source from a function taking a collection of requests.

@since2.0.0

makeBatched
(
(
requests: readonly SendEmail[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface SendEmail
SendEmail
>) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <void, SendEmailError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<void>;
readonly catch: (error: unknown) => SendEmailError;
}) => Effect.Effect<void, SendEmailError, 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<void>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmailBatch", {
RequestInit.method?: string
method
: "POST",
RequestInit.headers?: HeadersInit
headers
: {
"Content-Type": "application/json"
},
RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
emails: {
address: string;
text: string;
}[]
emails
:
requests: readonly SendEmail[]
requests
.
ReadonlyArray<SendEmail>.map<{
address: string;
text: string;
}>(callbackfn: (value: SendEmail, index: number, array: readonly SendEmail[]) => {
address: string;
text: string;
}, thisArg?: any): {
address: string;
text: string;
}[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
(({
address: string
address
,
text: string
text
}) => ({
address: string
address
,
text: string
text
}))
})
}).
Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError<{}>(args: void): SendEmailError
SendEmailError
()
}).
Pipeable.pipe<Effect.Effect<void, SendEmailError, never>, Effect.Effect<void[], SendEmailError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<void, SendEmailError, never>, ab: (_: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void[], SendEmailError, never>, bc: (_: Effect.Effect<void[], SendEmailError, never>) => Effect.Effect<void[], never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <Effect.Effect<void[], never, never>>(f: Effect.Effect<void[], never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void[], E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly SendEmail[]
requests
, (
request: SendEmail
request
) =>
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: SendEmail
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const void: Effect.Effect<void, never, never>
export void

Represents an effect that does nothing and produces no value.

When to Use

Use this effect when you need to represent an effect that does nothing. This is useful in scenarios where you need to satisfy an effect-based interface or control program flow without performing any operations. For example, it can be used in situations where you want to return an effect from a function but do not need to compute or return any result.

@since2.0.0

void
)
)
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const catchAll: <SendEmailError, void[], never, never>(f: (e: SendEmailError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, SendEmailError, R>) => Effect.Effect<void[] | A, never, R> (+1 overload)

Handles all errors in an effect by providing a fallback effect.

Details

This function catches any errors that may occur during the execution of an effect and allows you to handle them by specifying a fallback effect. This ensures that the program continues without failing by recovering from errors using the provided fallback logic.

Note: This function only handles recoverable errors. It will not recover from unrecoverable defects.

Example (Providing Recovery Logic for Recoverable Errors)

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.catchAll((error) =>
Effect.succeed(`Recovering from ${error._tag}`)
)
)

@seecatchAllCause for a version that can recover from both recoverable and unrecoverable errors.

@since2.0.0

catchAll
((
error: SendEmailError
error
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly SendEmail[]
requests
, (
request: SendEmail
request
) =>
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: SendEmail
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

在此配置中:

  • GetTodosResolver 处理多个Todo项目的获取。它被设置为标准解析器,因为我们假设它无法批处理。
  • GetUserByIdResolverSendEmailResolver 被配置为批处理解析器。此设置基于这些请求可以批量处理的假设,从而提高性能并减少API调用的数量。

现在我们已经设置了解析器,我们准备将所有部分结合起来定义我们的查询。此步骤将使我们能够在应用程序中有效地执行数据操作。

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Request
Request
,
import RequestResolver
RequestResolver
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// Requests
// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might
// fail with a GetTodosError
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
readonly
GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
}
// Create a tagged constructor for GetTodos requests
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
// Define a request to fetch a User by ID which might
// fail with a GetUserError
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
readonly
GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
readonly
GetUserById.id: number
id
: number
}
// Create a tagged constructor for GetUserById requests
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
// Define a request to send an email which might
// fail with a SendEmailError
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<void,
class SendEmailError
SendEmailError
> {
readonly
SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
readonly
SendEmail.address: string
address
: string
readonly
SendEmail.text: string
text
: string
}
// Create a tagged constructor for SendEmail requests
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
// ------------------------------
// Resolvers
// ------------------------------
72 collapsed lines
// Assuming GetTodos cannot be batched, we create a standard resolver
const
const GetTodosResolver: RequestResolver.RequestResolver<GetTodos, never>
GetTodosResolver
=
import RequestResolver
RequestResolver
.
const fromEffect: <never, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, never>) => RequestResolver.RequestResolver<GetTodos, never>

Constructs a data source from an effectual function.

@since2.0.0

fromEffect
(
(
_: GetTodos
_
:
interface GetTodos
GetTodos
):
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
interface Todo
Todo
[],
class GetTodosError
GetTodosError
> =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Todo[], GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<Todo[], GetTodosError, 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<Todo[]>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/todos").
Promise<Response>.then<Todo[], Todo[]>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => Todo[] | PromiseLike<Todo[]>) | null | undefined): Promise<Todo[]>

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
(
(
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>
),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
)
// Assuming GetUserById can be batched, we create a batched resolver
const
const GetUserByIdResolver: RequestResolver.RequestResolver<GetUserById, never>
GetUserByIdResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <GetUserById, never>(run: (requests: [GetUserById, ...GetUserById[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<GetUserById, never>

Constructs a data source from a function taking a collection of requests.

@since2.0.0

makeBatched
(
(
requests: readonly GetUserById[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface GetUserById
GetUserById
>) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <User[], GetUserError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<User[]>;
readonly catch: (error: unknown) => GetUserError;
}) => Effect.Effect<User[], GetUserError, 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<User[]>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/getUserByIdBatch", {
RequestInit.method?: string
method
: "POST",
RequestInit.headers?: HeadersInit
headers
: {
"Content-Type": "application/json"
},
RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
users: {
id: number;
}[]
users
:
requests: readonly GetUserById[]
requests
.
ReadonlyArray<GetUserById>.map<{
id: number;
}>(callbackfn: (value: GetUserById, index: number, array: readonly GetUserById[]) => {
id: number;
}, thisArg?: any): {
id: number;
}[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
(({
id: number
id
}) => ({
id: number
id
}))
})
}).
Promise<Response>.then<unknown, User[]>(onfulfilled?: ((value: Response) => unknown) | null | undefined, onrejected?: ((reason: any) => User[] | PromiseLike<User[]>) | null | undefined): Promise<unknown>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
()) as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface User
User
>>,
catch: (error: unknown) => GetUserError
catch
: () => new
constructor GetUserError<{}>(args: void): GetUserError
GetUserError
()
}).
Pipeable.pipe<Effect.Effect<User[], GetUserError, never>, Effect.Effect<void[], GetUserError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<User[], GetUserError, never>, ab: (_: Effect.Effect<User[], GetUserError, never>) => Effect.Effect<void[], GetUserError, never>, bc: (_: Effect.Effect<void[], GetUserError, never>) => Effect.Effect<void[], never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User[], Effect.Effect<void[], never, never>>(f: (a: User[]) => Effect.Effect<void[], never, never>) => <E, R>(self: Effect.Effect<User[], E, R>) => Effect.Effect<void[], E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
users: User[]
users
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly GetUserById[]
requests
, (
request: GetUserById
request
,
index: number
index
) =>
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: GetUserById
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <User>(value: User) => Effect.Effect<User, 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
(
users: User[]
users
[
index: number
index
]!))
)
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const catchAll: <GetUserError, void[], never, never>(f: (e: GetUserError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, GetUserError, R>) => Effect.Effect<void[] | A, never, R> (+1 overload)

Handles all errors in an effect by providing a fallback effect.

Details

This function catches any errors that may occur during the execution of an effect and allows you to handle them by specifying a fallback effect. This ensures that the program continues without failing by recovering from errors using the provided fallback logic.

Note: This function only handles recoverable errors. It will not recover from unrecoverable defects.

Example (Providing Recovery Logic for Recoverable Errors)

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.catchAll((error) =>
Effect.succeed(`Recovering from ${error._tag}`)
)
)

@seecatchAllCause for a version that can recover from both recoverable and unrecoverable errors.

@since2.0.0

catchAll
((
error: GetUserError
error
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly GetUserById[]>(self: readonly GetUserById[], f: (a: GetUserById, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly GetUserById[]
requests
, (
request: GetUserById
request
) =>
import Request
Request
.
const completeEffect: <GetUserById, never>(self: GetUserById, effect: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: GetUserById
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <GetUserError>(error: GetUserError) => Effect.Effect<never, GetUserError, 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
(
error: GetUserError
error
))
)
)
)
)
// Assuming SendEmail can be batched, we create a batched resolver
const
const SendEmailResolver: RequestResolver.RequestResolver<SendEmail, never>
SendEmailResolver
=
import RequestResolver
RequestResolver
.
const makeBatched: <SendEmail, never>(run: (requests: [SendEmail, ...SendEmail[]]) => Effect.Effect<void, never, never>) => RequestResolver.RequestResolver<SendEmail, never>

Constructs a data source from a function taking a collection of requests.

@since2.0.0

makeBatched
(
(
requests: readonly SendEmail[]
requests
:
interface ReadonlyArray<T>
ReadonlyArray
<
interface SendEmail
SendEmail
>) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <void, SendEmailError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<void>;
readonly catch: (error: unknown) => SendEmailError;
}) => Effect.Effect<void, SendEmailError, 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<void>
try
: () =>
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
("https://api.example.demo/sendEmailBatch", {
RequestInit.method?: string
method
: "POST",
RequestInit.headers?: HeadersInit
headers
: {
"Content-Type": "application/json"
},
RequestInit.body?: BodyInit
body
:
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
emails: {
address: string;
text: string;
}[]
emails
:
requests: readonly SendEmail[]
requests
.
ReadonlyArray<SendEmail>.map<{
address: string;
text: string;
}>(callbackfn: (value: SendEmail, index: number, array: readonly SendEmail[]) => {
address: string;
text: string;
}, thisArg?: any): {
address: string;
text: string;
}[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
(({
address: string
address
,
text: string
text
}) => ({
address: string
address
,
text: string
text
}))
})
}).
Promise<Response>.then<void, never>(onfulfilled?: ((value: Response) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void>),
catch: (error: unknown) => SendEmailError
catch
: () => new
constructor SendEmailError<{}>(args: void): SendEmailError
SendEmailError
()
}).
Pipeable.pipe<Effect.Effect<void, SendEmailError, never>, Effect.Effect<void[], SendEmailError, never>, Effect.Effect<void[], never, never>>(this: Effect.Effect<void, SendEmailError, never>, ab: (_: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void[], SendEmailError, never>, bc: (_: Effect.Effect<void[], SendEmailError, never>) => Effect.Effect<void[], never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <Effect.Effect<void[], never, never>>(f: Effect.Effect<void[], never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void[], E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly SendEmail[]
requests
, (
request: SendEmail
request
) =>
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: SendEmail
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const void: Effect.Effect<void, never, never>
export void

Represents an effect that does nothing and produces no value.

When to Use

Use this effect when you need to represent an effect that does nothing. This is useful in scenarios where you need to satisfy an effect-based interface or control program flow without performing any operations. For example, it can be used in situations where you want to return an effect from a function but do not need to compute or return any result.

@since2.0.0

void
)
)
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const catchAll: <SendEmailError, void[], never, never>(f: (e: SendEmailError) => Effect.Effect<void[], never, never>) => <A, R>(self: Effect.Effect<A, SendEmailError, R>) => Effect.Effect<void[] | A, never, R> (+1 overload)

Handles all errors in an effect by providing a fallback effect.

Details

This function catches any errors that may occur during the execution of an effect and allows you to handle them by specifying a fallback effect. This ensures that the program continues without failing by recovering from errors using the provided fallback logic.

Note: This function only handles recoverable errors. It will not recover from unrecoverable defects.

Example (Providing Recovery Logic for Recoverable Errors)

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.catchAll((error) =>
Effect.succeed(`Recovering from ${error._tag}`)
)
)

@seecatchAllCause for a version that can recover from both recoverable and unrecoverable errors.

@since2.0.0

catchAll
((
error: SendEmailError
error
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const forEach: <void, never, never, readonly SendEmail[]>(self: readonly SendEmail[], f: (a: SendEmail, i: number) => Effect.Effect<void, never, never>, options?: {
readonly concurrency?: Concurrency | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly discard?: false | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => Effect.Effect<void[], never, never> (+3 overloads)

Executes an effectful operation for each element in an Iterable.

Details

This function applies a provided operation to each element in the iterable, producing a new effect that returns an array of results.

If any effect fails, the iteration stops immediately (short-circuiting), and the error is propagated.

Concurrency

The concurrency option controls how many operations are performed concurrently. By default, the operations are performed sequentially.

Discarding Results

If the discard option is set to true, the intermediate results are not collected, and the final result of the operation is void.

Example (Applying Effects to Iterable Elements)

import { Effect, Console } from "effect"
const result = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2))
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// [ 2, 4, 6, 8, 10 ]

Example (Discarding Results)

import { Effect, Console } from "effect"
// Apply effects but discard the results
const result = Effect.forEach(
[1, 2, 3, 4, 5],
(n, index) =>
Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)),
{ discard: true }
)
Effect.runPromise(result).then(console.log)
// Output:
// Currently at index 0
// Currently at index 1
// Currently at index 2
// Currently at index 3
// Currently at index 4
// undefined

@seeall for combining multiple effects into one.

@since2.0.0

forEach
(
requests: readonly SendEmail[]
requests
, (
request: SendEmail
request
) =>
import Request
Request
.
const completeEffect: <SendEmail, never>(self: SendEmail, effect: Effect.Effect<void, SendEmailError, never>) => Effect.Effect<void, never, never> (+1 overload)

Complete a Request with the specified effectful computation, failing the request with the error from the effect workflow if it fails, and completing the request with the value of the effect workflow if it succeeds.

@since2.0.0

completeEffect
(
request: SendEmail
request
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <SendEmailError>(error: SendEmailError) => Effect.Effect<never, SendEmailError, 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
(
error: SendEmailError
error
))
)
)
)
)
// ------------------------------
// Queries
// ------------------------------
// 定义一个查询来获取所有Todo项目
const
const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> =
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const request: <RequestResolver.RequestResolver<GetTodos, never>, GetTodos>(self: GetTodos, dataSource: RequestResolver.RequestResolver<GetTodos, never>) => Effect.Effect<Todo[], GetTodosError, never> (+1 overload)

@since2.0.0

request
(
const GetTodos: Request.Request<out A, out E = never>.Constructor
(args: Omit<GetTodos, "_tag" | typeof Request.RequestTypeId>) => GetTodos
GetTodos
({}),
const GetTodosResolver: RequestResolver.RequestResolver<GetTodos, never>
GetTodosResolver
)
// 定义一个查询通过ID获取用户
const
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
= (
id: number
id
: number) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const request: <RequestResolver.RequestResolver<GetUserById, never>, GetUserById>(self: GetUserById, dataSource: RequestResolver.RequestResolver<GetUserById, never>) => Effect.Effect<User, GetUserError, never> (+1 overload)

@since2.0.0

request
(
const GetUserById: Request.Request<out A, out E = never>.Constructor
(args: Omit<GetUserById, "_tag" | typeof Request.RequestTypeId>) => GetUserById
GetUserById
({
id: number
id
}),
const GetUserByIdResolver: RequestResolver.RequestResolver<GetUserById, never>
GetUserByIdResolver
)
// 定义一个查询向特定地址发送电子邮件
const
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
= (
address: string
address
: string,
text: string
text
: string) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const request: <RequestResolver.RequestResolver<SendEmail, never>, SendEmail>(self: SendEmail, dataSource: RequestResolver.RequestResolver<SendEmail, never>) => Effect.Effect<void, SendEmailError, never> (+1 overload)

@since2.0.0

request
(
const SendEmail: Request.Request<out A, out E = never>.Constructor
(args: Omit<SendEmail, "_tag" | typeof Request.RequestTypeId>) => SendEmail
SendEmail
({
address: string
address
,
text: string
text
}),
const SendEmailResolver: RequestResolver.RequestResolver<SendEmail, never>
SendEmailResolver
)
// 组合getUserById和sendEmail向特定用户发送电子邮件
const
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
= (
id: number
id
: number,
message: string
message
: string) =>
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
id: number
id
).
Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<User, GetUserError, never>, ab: (_: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, GetUserError | SendEmailError, never>): Effect.Effect<void, GetUserError | SendEmailError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User, Effect.Effect<void, SendEmailError, never>>(f: (a: User) => Effect.Effect<void, SendEmailError, never>) => <E, R>(self: Effect.Effect<User, E, R>) => Effect.Effect<void, SendEmailError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
user: User
user
) =>
const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail
(
user: User
user
.
User.email: string
email
,
message: string
message
))
)
// 使用getUserById获取Todo的所有者,然后向他们发送电子邮件通知
const
const notifyOwner: (todo: Todo) => Effect.Effect<void, GetUserError | SendEmailError, never>
notifyOwner
= (
todo: Todo
todo
:
interface Todo
Todo
) =>
const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById
(
todo: Todo
todo
.
Todo.ownerId: number
ownerId
).
Pipeable.pipe<Effect.Effect<User, GetUserError, never>, Effect.Effect<void, GetUserError | SendEmailError, never>>(this: Effect.Effect<User, GetUserError, never>, ab: (_: Effect.Effect<User, GetUserError, never>) => Effect.Effect<void, GetUserError | SendEmailError, never>): Effect.Effect<void, GetUserError | SendEmailError, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <User, Effect.Effect<void, GetUserError | SendEmailError, never>>(f: (a: User) => Effect.Effect<void, GetUserError | SendEmailError, never>) => <E, R>(self: Effect.Effect<User, E, R>) => Effect.Effect<void, GetUserError | SendEmailError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
((
user: User
user
) =>
const sendEmailToUser: (id: number, message: string) => Effect.Effect<void, GetUserError | SendEmailError, never>
sendEmailToUser
(
user: User
user
.
User.id: number
id
, `hey ${
user: User
user
.
User.name: string
name
} you got a todo!`)
)
)

通过使用Effect.request函数,我们有效地将解析器与请求模型集成。这种方法确保每个查询都使用适当的解析器得到最优解析。

尽管代码结构看起来与早期示例相似,但使用解析器通过优化请求处理方式和减少不必要的API调用显著提高了效率。

// 此程序只会进行3次API调用
// 1次调用获取待办事项 + 1次批量调用获取用户 + 1次批量调用发送电子邮件
const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
batching: true
})
})

在最终设置中,无论待办事项的数量如何,此程序只会对API执行3次查询。这与传统方法形成鲜明对比,传统方法可能会执行1 + 2n次查询,其中n是待办事项的数量。这代表了效率的显著提升,特别是对于具有大量数据交互的应用程序。

可以使用Effect.withRequestBatching实用程序以以下方式在本地禁用批处理:

// 为此效果禁用批处理
const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(Effect.withRequestBatching(false))

在复杂的应用程序中,解析器通常需要访问共享服务或配置来有效处理请求。然而,在提供必要上下文的同时保持批处理请求的能力可能具有挑战性。在这里,我们将探讨如何在解析器中管理上下文,以确保批处理能力不受影响。

When creating request resolvers, it’s crucial to manage the context carefully. Providing too much context or providing varying services to resolvers can make them incompatible for batching. To prevent such issues, the context for the resolver used in Effect.request is explicitly set to never. This forces developers to clearly define how the context is accessed and used within resolvers.

Consider the following example where we set up an HTTP service that the resolvers can use to execute API calls:

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Context

@since2.0.0

@since2.0.0

Context
,
import RequestResolver
RequestResolver
,
import Request
Request
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// Requests
// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might
// fail with a GetTodosError
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
readonly
GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
}
// Create a tagged constructor for GetTodos requests
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
// Define a request to fetch a User by ID which might
// fail with a GetUserError
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
readonly
GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
readonly
GetUserById.id: number
id
: number
}
// Create a tagged constructor for GetUserById requests
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
// Define a request to send an email which might
// fail with a SendEmailError
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<void,
class SendEmailError
SendEmailError
> {
readonly
SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
readonly
SendEmail.address: string
address
: string
readonly
SendEmail.text: string
text
: string
}
// Create a tagged constructor for SendEmail requests
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
// ------------------------------
// Resolvers With Context
// ------------------------------
class
class HttpService
HttpService
extends
import Context

@since2.0.0

@since2.0.0

Context
.
const Tag: <"HttpService">(id: "HttpService") => <Self, Shape>() => Context.TagClass<Self, "HttpService", Shape>

@example

import * as assert from "node:assert"
import { Context, Layer } from "effect"
class MyTag extends Context.Tag("MyTag")<
MyTag,
{ readonly myNum: number }
>() {
static Live = Layer.succeed(this, { myNum: 108 })
}

@since2.0.0

Tag
("HttpService")<
class HttpService
HttpService
,
{
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
: typeof
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
}
>() {}
const
const GetTodosResolver: Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>
GetTodosResolver
=
// we create a normal resolver like we did before
import RequestResolver
RequestResolver
.
const fromEffect: <HttpService, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, HttpService>) => RequestResolver.RequestResolver<GetTodos, HttpService>

Constructs a data source from an effectual function.

@since2.0.0

fromEffect
((
_: GetTodos
_
:
interface GetTodos
GetTodos
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <{
fetch: typeof fetch;
}, never, HttpService, Effect.Effect<Todo[], GetTodosError, never>>(self: Effect.Effect<{
fetch: typeof fetch;
}, never, HttpService>, f: (a: {
fetch: typeof fetch;
}) => Effect.Effect<Todo[], GetTodosError, never>) => Effect.Effect<Todo[], GetTodosError, HttpService> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
(
class HttpService
HttpService
, (
http: {
fetch: typeof fetch;
}
http
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Todo[], GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<Todo[], GetTodosError, 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<Todo[]>
try
: () =>
http: {
fetch: typeof fetch;
}
http
.
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
("https://api.example.demo/todos")
.
Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<Todo[]>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
)
).
Pipeable.pipe<RequestResolver.RequestResolver<GetTodos, HttpService>, Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>>(this: RequestResolver.RequestResolver<GetTodos, HttpService>, ab: (_: RequestResolver.RequestResolver<GetTodos, HttpService>) => Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>): Effect.Effect<...> (+21 overloads)
pipe
(
// we list the tags that the resolver can access
import RequestResolver
RequestResolver
.
const contextFromServices: <[typeof HttpService]>(services_0: typeof HttpService) => <R, A>(self: RequestResolver.RequestResolver<A, R>) => Effect.Effect<RequestResolver.RequestResolver<A, Exclude<R, HttpService>>, never, HttpService>

@since2.0.0

contextFromServices
(
class HttpService
HttpService
)
)

我们现在可以看到GetTodosResolver的类型不再是RequestResolver,而是:

const GetTodosResolver: Effect<
RequestResolver<GetTodos, never>,
never,
HttpService
>

这是一个访问HttpService并返回具有最小上下文准备使用的组合解析器的效果。

Once we have such effect we can directly use it in our query definition:

const getTodos: Effect.Effect<Todo[], GetTodosError, HttpService> =
Effect.request(GetTodos({}), GetTodosResolver)

We can see that the Effect correctly requires HttpService to be provided.

Alternatively you can create RequestResolvers as part of layers direcly accessing or closing over context from construction.

Example

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Context

@since2.0.0

@since2.0.0

Context
,
import RequestResolver
RequestResolver
,
import Request
Request
,
import Layer
Layer
,
import Data
Data
} from "effect"
// ------------------------------
// Model
// ------------------------------
19 collapsed lines
interface
interface User
User
{
readonly
User._tag: "User"
_tag
: "User"
readonly
User.id: number
id
: number
readonly
User.name: string
name
: string
readonly
User.email: string
email
: string
}
class
class GetUserError
GetUserError
extends
import Data
Data
.
const TaggedError: <"GetUserError">(tag: "GetUserError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetUserError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetUserError")<{}> {}
interface
interface Todo
Todo
{
readonly
Todo._tag: "Todo"
_tag
: "Todo"
readonly
Todo.id: number
id
: number
readonly
Todo.message: string
message
: string
readonly
Todo.ownerId: number
ownerId
: number
}
class
class GetTodosError
GetTodosError
extends
import Data
Data
.
const TaggedError: <"GetTodosError">(tag: "GetTodosError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "GetTodosError";
} & Readonly<A>

@since2.0.0

TaggedError
("GetTodosError")<{}> {}
class
class SendEmailError
SendEmailError
extends
import Data
Data
.
const TaggedError: <"SendEmailError">(tag: "SendEmailError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "SendEmailError";
} & Readonly<A>

@since2.0.0

TaggedError
("SendEmailError")<{}> {}
// ------------------------------
// Requests
// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might
// fail with a GetTodosError
interface
interface GetTodos
GetTodos
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
> {
readonly
GetTodos._tag: "GetTodos"
_tag
: "GetTodos"
}
// Create a tagged constructor for GetTodos requests
const
const GetTodos: Request.Request.Constructor<GetTodos, "_tag">
GetTodos
=
import Request
Request
.
const tagged: <GetTodos>(tag: "GetTodos") => Request.Request<out A, out E = never>.Constructor<GetTodos, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetTodos
GetTodos
>("GetTodos")
// Define a request to fetch a User by ID which might
// fail with a GetUserError
interface
interface GetUserById
GetUserById
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<
interface User
User
,
class GetUserError
GetUserError
> {
readonly
GetUserById._tag: "GetUserById"
_tag
: "GetUserById"
readonly
GetUserById.id: number
id
: number
}
// Create a tagged constructor for GetUserById requests
const
const GetUserById: Request.Request.Constructor<GetUserById, "_tag">
GetUserById
=
import Request
Request
.
const tagged: <GetUserById>(tag: "GetUserById") => Request.Request<out A, out E = never>.Constructor<GetUserById, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface GetUserById
GetUserById
>("GetUserById")
// Define a request to send an email which might
// fail with a SendEmailError
interface
interface SendEmail
SendEmail
extends
import Request
Request
.
interface Request<out A, out E = never>

A Request<A, E> is a request from a data source for a value of type A that may fail with an E.

@since2.0.0

@since2.0.0

Request
<void,
class SendEmailError
SendEmailError
> {
readonly
SendEmail._tag: "SendEmail"
_tag
: "SendEmail"
readonly
SendEmail.address: string
address
: string
readonly
SendEmail.text: string
text
: string
}
// Create a tagged constructor for SendEmail requests
const
const SendEmail: Request.Request.Constructor<SendEmail, "_tag">
SendEmail
=
import Request
Request
.
const tagged: <SendEmail>(tag: "SendEmail") => Request.Request<out A, out E = never>.Constructor<SendEmail, "_tag">

Constructs a new Request.

@since2.0.0

tagged
<
interface SendEmail
SendEmail
>("SendEmail")
// ------------------------------
// Resolvers With Context
// ------------------------------
21 collapsed lines
class
class HttpService
HttpService
extends
import Context

@since2.0.0

@since2.0.0

Context
.
const Tag: <"HttpService">(id: "HttpService") => <Self, Shape>() => Context.TagClass<Self, "HttpService", Shape>

@example

import * as assert from "node:assert"
import { Context, Layer } from "effect"
class MyTag extends Context.Tag("MyTag")<
MyTag,
{ readonly myNum: number }
>() {
static Live = Layer.succeed(this, { myNum: 108 })
}

@since2.0.0

Tag
("HttpService")<
class HttpService
HttpService
,
{
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
: typeof
function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>
fetch
}
>() {}
const
const GetTodosResolver: Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>
GetTodosResolver
=
// we create a normal resolver like we did before
import RequestResolver
RequestResolver
.
const fromEffect: <HttpService, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, HttpService>) => RequestResolver.RequestResolver<GetTodos, HttpService>

Constructs a data source from an effectual function.

@since2.0.0

fromEffect
((
_: GetTodos
_
:
interface GetTodos
GetTodos
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <{
fetch: typeof fetch;
}, never, HttpService, Effect.Effect<Todo[], GetTodosError, never>>(self: Effect.Effect<{
fetch: typeof fetch;
}, never, HttpService>, f: (a: {
fetch: typeof fetch;
}) => Effect.Effect<Todo[], GetTodosError, never>) => Effect.Effect<Todo[], GetTodosError, HttpService> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
(
class HttpService
HttpService
, (
http: {
fetch: typeof fetch;
}
http
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <Todo[], GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<Todo[]>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<Todo[], GetTodosError, 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<Todo[]>
try
: () =>
http: {
fetch: typeof fetch;
}
http
.
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
("https://api.example.demo/todos")
.
Promise<Response>.then<Todo[], never>(onfulfilled?: ((value: Response) => Todo[] | PromiseLike<Todo[]>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<Todo[]>

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
((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Array<T>
Array
<
interface Todo
Todo
>>),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
)
).
Pipeable.pipe<RequestResolver.RequestResolver<GetTodos, HttpService>, Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>>(this: RequestResolver.RequestResolver<GetTodos, HttpService>, ab: (_: RequestResolver.RequestResolver<GetTodos, HttpService>) => Effect.Effect<RequestResolver.RequestResolver<GetTodos, never>, never, HttpService>): Effect.Effect<...> (+21 overloads)
pipe
(
// we list the tags that the resolver can access
import RequestResolver
RequestResolver
.
const contextFromServices: <[typeof HttpService]>(services_0: typeof HttpService) => <R, A>(self: RequestResolver.RequestResolver<A, R>) => Effect.Effect<RequestResolver.RequestResolver<A, Exclude<R, HttpService>>, never, HttpService>

@since2.0.0

contextFromServices
(
class HttpService
HttpService
)
)
// ------------------------------
// Layers
// ------------------------------
class
class TodosService
TodosService
extends
import Context

@since2.0.0

@since2.0.0

Context
.
const Tag: <"TodosService">(id: "TodosService") => <Self, Shape>() => Context.TagClass<Self, "TodosService", Shape>

@example

import * as assert from "node:assert"
import { Context, Layer } from "effect"
class MyTag extends Context.Tag("MyTag")<
MyTag,
{ readonly myNum: number }
>() {
static Live = Layer.succeed(this, { myNum: 108 })
}

@since2.0.0

Tag
("TodosService")<
class TodosService
TodosService
,
{
getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
>
}
>() {}
const
const TodosServiceLive: Layer.Layer<TodosService, never, HttpService>
TodosServiceLive
=
import Layer
Layer
.
const effect: <TodosService, {
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}, never, HttpService>(tag: Context.Tag<TodosService, {
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}>, effect: Effect.Effect<{
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}, never, HttpService>) => Layer.Layer<TodosService, never, HttpService> (+1 overload)

Constructs a layer from the specified effect.

@since2.0.0

effect
(
class TodosService
TodosService
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Context.Tag<HttpService, {
fetch: typeof fetch;
}>>, {
getTodos: Effect.Effect<Todo[], GetTodosError, never>;
}>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Context.Tag<HttpService, {
fetch: typeof fetch;
}>>, {
getTodos: Effect.Effect<Todo[], GetTodosError, never>;
}, never>) => Effect.Effect<{
getTodos: Effect.Effect<Todo[], GetTodosError, never>;
}, never, HttpService> (+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 http: {
fetch: typeof fetch;
}
http
= yield*
class HttpService
HttpService
const
const resolver: RequestResolver.RequestResolver<GetTodos, never>
resolver
=
import RequestResolver
RequestResolver
.
const fromEffect: <never, GetTodos>(f: (a: GetTodos) => Effect.Effect<Todo[], GetTodosError, never>) => RequestResolver.RequestResolver<GetTodos, never>

Constructs a data source from an effectual function.

@since2.0.0

fromEffect
((
_: GetTodos
_
:
interface GetTodos
GetTodos
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tryPromise: <any, GetTodosError>(options: {
readonly try: (signal: AbortSignal) => PromiseLike<any>;
readonly catch: (error: unknown) => GetTodosError;
}) => Effect.Effect<any, GetTodosError, 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<any>
try
: () =>
const http: {
fetch: typeof fetch;
}
http
.
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
fetch
("https://api.example.demo/todos")
.
Promise<Response>.then<any, Todo[]>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => Todo[] | PromiseLike<Todo[]>) | null | undefined): Promise<any>

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
<any,
interface Todo
Todo
[]>((
res: Response
res
) =>
res: Response
res
.
BodyMixin.json: () => Promise<unknown>
json
()),
catch: (error: unknown) => GetTodosError
catch
: () => new
constructor GetTodosError<{}>(args: void): GetTodosError
GetTodosError
()
})
)
return {
getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const request: <RequestResolver.RequestResolver<GetTodos, never>, GetTodos>(self: GetTodos, dataSource: RequestResolver.RequestResolver<GetTodos, never>) => Effect.Effect<Todo[], GetTodosError, never> (+1 overload)

@since2.0.0

request
(
const GetTodos: Request.Request<out A, out E = never>.Constructor
(args: Omit<GetTodos, "_tag" | typeof Request.RequestTypeId>) => GetTodos
GetTodos
({}),
const resolver: RequestResolver.RequestResolver<GetTodos, never>
resolver
)
}
})
)
const
const getTodos: Effect.Effect<Todo[], GetTodosError, TodosService>
getTodos
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

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

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
interface Array<T>
Array
<
interface Todo
Todo
>,
class GetTodosError
GetTodosError
,
class TodosService
TodosService
> =
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <{
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}, never, TodosService, Effect.Effect<Todo[], GetTodosError, never>>(self: Effect.Effect<{
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}, never, TodosService>, f: (a: {
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}) => Effect.Effect<Todo[], GetTodosError, never>) => Effect.Effect<Todo[], GetTodosError, TodosService> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

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))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
(
class TodosService
TodosService
, (
service: {
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}
service
) =>
service: {
getTodos: Effect.Effect<Array<Todo>, GetTodosError>;
}
service
.
getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos
)

考虑到层是连接服务的自然原语,这种方式对于大多数情况来说可能是最好的。

虽然我们已经显著优化了请求批处理,但还有另一个可以提高应用程序效率的领域:缓存。如果没有缓存,即使有优化的批处理,相同的请求也可能被执行多次,导致不必要的数据获取。

在Effect库中,缓存通过内置实用程序处理,允许临时存储请求,防止需要重新获取未更改的数据。此功能对于减少服务器和网络负载至关重要,特别是在频繁进行类似请求的应用程序中。

以下是如何为getUserById查询实现缓存:

const getUserById = (id: number) =>
Effect.request(GetUserById({ id }), GetUserByIdResolver).pipe(
Effect.withRequestCaching(true)
)

假设您已正确连接所有内容:

const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(Effect.repeat(Schedule.fixed("10 seconds")))

通过此程序,getTodos操作检索每个用户的待办事项。然后,使用Effect.forEach函数并发通知每个待办事项的所有者,而无需等待通知完成。

repeat函数应用于整个操作链,它确保程序使用固定计划每10秒重复一次。这意味着整个过程,包括获取待办事项和发送通知,将以10秒间隔重复执行。

该程序包含缓存机制,防止在1分钟内多次执行相同的GetUserById操作。这种默认缓存行为有助于优化程序执行并减少获取用户数据的不必要请求。

此外,该程序设计为批量发送电子邮件,允许高效处理和更好地利用资源。

在实际应用程序中,有效的缓存策略可以通过减少冗余数据获取显著提高性能。Effect库提供灵活的缓存机制,可以针对应用程序的特定部分进行定制或全局应用。

可能存在应用程序不同部分具有独特缓存要求的场景——有些可能受益于本地化缓存,而其他可能需要全局缓存设置。让我们探讨如何配置自定义缓存以满足这些不同的需求。

以下是如何创建自定义缓存并将其应用于应用程序的一部分。此示例演示了设置每10秒重复任务的缓存,使用特定参数(如容量和TTL(生存时间))缓存请求。

// 创建具有特定容量和TTL的自定义缓存
const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(
Effect.repeat(Schedule.fixed("10 seconds")),
Effect.provide(
Layer.setRequestCache(
Request.makeCache({ capacity: 256, timeToLive: "60 minutes" })
)
)
)

您还可以使用Request.makeCache构造缓存,并使用Effect.withRequestCache将其直接应用于特定程序。此方法确保来自指定程序的所有请求都通过自定义缓存管理,前提是启用了缓存。

// 将自定义缓存应用于程序
const customCache = Request.makeCache({ capacity: 256, timeToLive: "60 minutes" })
const program = Effect.gen(function* () {
const todos = yield* getTodos
yield* Effect.forEach(todos, (todo) => notifyOwner(todo), {
concurrency: "unbounded"
})
}).pipe(
Effect.repeat(Schedule.fixed("10 seconds")),
Effect.withRequestCache(customCache)
)

这种方法提供对缓存行为的细粒度控制,允许您根据应用程序不同部分的特定需求优化性能。