批处理
在典型的应用程序开发中,当与外部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>
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>
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>
TaggedError("SendEmailError")<{}> {}让我们定义与外部API交互的函数,处理常见操作,如获取待办事项、检索用户详细信息和发送电子邮件。
import { import Effect
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>
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>
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>
TaggedError("SendEmailError")<{}> {}
// ------------------------------// API// ------------------------------
// 从外部API获取待办事项列表const const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos = import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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
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>
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>
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>
TaggedError("SendEmailError")<{}> {}
// ------------------------------// API// ------------------------------
46 collapsed lines
// Fetches a list of todos from an external APIconst const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos = import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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 APIconst const getUserById: (id: number) => Effect.Effect<User, GetUserError, never>
getUserById = (id: number
id: number) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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 APIconst const sendEmail: (address: string, text: string) => Effect.Effect<void, SendEmailError, never>
sendEmail = (address: string
address: string, text: string
text: string) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
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 firstconst 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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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 emailconst 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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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
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}`})
gen(function* () { const const todos: Todo[]
todos = yield* const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos yield* import Effect
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 resultsconst 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
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调用。
让我们假设getUserById和sendEmail可以进行批处理。这意味着我们可以在单个HTTP调用中发送多个请求,减少API请求的数量并提高性能。
批处理分步指南
-
声明请求: 我们将首先将请求转换为结构化数据模型。这涉及详细说明输入参数、预期输出和可能的错误。以这种方式构造请求不仅有助于高效管理数据,还有助于比较不同的请求以了解它们是否引用相同的输入参数。
-
声明解析器: 解析器旨在同时处理多个请求。通过利用比较请求的能力(确保它们引用相同的输入参数),解析器可以一次执行多个请求,最大化批处理的效用。
-
定义查询: 最后,我们将定义利用这些批处理解析器执行操作的查询。此步骤将结构化请求及其相应的解析器绑定到应用程序的功能组件中。
我们将使用数据源可能支持的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>
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>
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>
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.
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.
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.
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.
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.
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.
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
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>
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>
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>
TaggedError("SendEmailError")<{}> {}
// ------------------------------// Requests// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might// fail with a GetTodosErrorinterface 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.
Request<interface Array<T>
Array<interface Todo
Todo>, class GetTodosError
GetTodosError> { readonly GetTodos._tag: "GetTodos"
_tag: "GetTodos"}
// Create a tagged constructor for GetTodos requestsconst 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.
tagged<interface GetTodos
GetTodos>("GetTodos")
// Define a request to fetch a User by ID which might// fail with a GetUserErrorinterface 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.
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 requestsconst 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.
tagged<interface GetUserById
GetUserById>("GetUserById")
// Define a request to send an email which might// fail with a SendEmailErrorinterface 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.
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 requestsconst 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.
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.
fromEffect( (_: GetTodos
_: interface GetTodos
GetTodos): import Effect
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.
Effect<interface Todo
Todo[], class GetTodosError
GetTodosError> => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
makeBatched( (requests: readonly GetUserById[]
requests: interface ReadonlyArray<T>
ReadonlyArray<interface GetUserById
GetUserById>) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
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.
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
andThen((users: User[]
users) => import Effect
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 resultsconst 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
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.
completeEffect(request: GetUserById
request, import Effect
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)
succeed(users: User[]
users[index: number
index]!)) ) ), import Effect
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}`) ))
catchAll((error: GetUserError
error) => import Effect
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 resultsconst 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
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.
completeEffect(request: GetUserById
request, import Effect
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"))
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.
makeBatched( (requests: readonly SendEmail[]
requests: interface ReadonlyArray<T>
ReadonlyArray<interface SendEmail
SendEmail>) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
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.
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
andThen( import Effect
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 resultsconst 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
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.
completeEffect(request: SendEmail
request, import Effect
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.
void) ) ), import Effect
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}`) ))
catchAll((error: SendEmailError
error) => import Effect
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 resultsconst 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
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.
completeEffect(request: SendEmail
request, import Effect
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"))
fail(error: SendEmailError
error)) ) ) ))在此配置中:
- GetTodosResolver 处理多个
Todo项目的获取。它被设置为标准解析器,因为我们假设它无法批处理。 - GetUserByIdResolver 和 SendEmailResolver 被配置为批处理解析器。此设置基于这些请求可以批量处理的假设,从而提高性能并减少API调用的数量。
现在我们已经设置了解析器,我们准备将所有部分结合起来定义我们的查询。此步骤将使我们能够在应用程序中有效地执行数据操作。
import { import Effect
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>
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>
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>
TaggedError("SendEmailError")<{}> {}
// ------------------------------// Requests// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might// fail with a GetTodosErrorinterface 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.
Request<interface Array<T>
Array<interface Todo
Todo>, class GetTodosError
GetTodosError> { readonly GetTodos._tag: "GetTodos"
_tag: "GetTodos"}
// Create a tagged constructor for GetTodos requestsconst 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.
tagged<interface GetTodos
GetTodos>("GetTodos")
// Define a request to fetch a User by ID which might// fail with a GetUserErrorinterface 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.
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 requestsconst 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.
tagged<interface GetUserById
GetUserById>("GetUserById")
// Define a request to send an email which might// fail with a SendEmailErrorinterface 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.
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 requestsconst 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.
tagged<interface SendEmail
SendEmail>("SendEmail")
// ------------------------------// Resolvers// ------------------------------
72 collapsed lines
// Assuming GetTodos cannot be batched, we create a standard resolverconst 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.
fromEffect( (_: GetTodos
_: interface GetTodos
GetTodos): import Effect
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.
Effect<interface Todo
Todo[], class GetTodosError
GetTodosError> => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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 resolverconst 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.
makeBatched( (requests: readonly GetUserById[]
requests: interface ReadonlyArray<T>
ReadonlyArray<interface GetUserById
GetUserById>) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
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.
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
andThen((users: User[]
users) => import Effect
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 resultsconst 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
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.
completeEffect(request: GetUserById
request, import Effect
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)
succeed(users: User[]
users[index: number
index]!)) ) ), import Effect
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}`) ))
catchAll((error: GetUserError
error) => import Effect
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 resultsconst 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
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.
completeEffect(request: GetUserById
request, import Effect
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"))
fail(error: GetUserError
error)) ) ) ))
// Assuming SendEmail can be batched, we create a batched resolverconst 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.
makeBatched( (requests: readonly SendEmail[]
requests: interface ReadonlyArray<T>
ReadonlyArray<interface SendEmail
SendEmail>) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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.
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.
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
andThen( import Effect
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 resultsconst 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
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.
completeEffect(request: SendEmail
request, import Effect
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.
void) ) ), import Effect
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}`) ))
catchAll((error: SendEmailError
error) => import Effect
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 resultsconst 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
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.
completeEffect(request: SendEmail
request, import Effect
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"))
fail(error: SendEmailError
error)) ) ) ))
// ------------------------------// Queries// ------------------------------
// 定义一个查询来获取所有Todo项目const const getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos: import Effect
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.
Effect< interface Array<T>
Array<interface Todo
Todo>, class GetTodosError
GetTodosError> = import Effect
Effect.const request: <RequestResolver.RequestResolver<GetTodos, never>, GetTodos>(self: GetTodos, dataSource: RequestResolver.RequestResolver<GetTodos, never>) => Effect.Effect<Todo[], GetTodosError, never> (+1 overload)
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
Effect.const request: <RequestResolver.RequestResolver<GetUserById, never>, GetUserById>(self: GetUserById, dataSource: RequestResolver.RequestResolver<GetUserById, never>) => Effect.Effect<User, GetUserError, never> (+1 overload)
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
Effect.const request: <RequestResolver.RequestResolver<SendEmail, never>, SendEmail>(self: SendEmail, dataSource: RequestResolver.RequestResolver<SendEmail, never>) => Effect.Effect<void, SendEmailError, never> (+1 overload)
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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
Effect, import Context
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>
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>
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>
TaggedError("SendEmailError")<{}> {}
// ------------------------------// Requests// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might// fail with a GetTodosErrorinterface 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.
Request<interface Array<T>
Array<interface Todo
Todo>, class GetTodosError
GetTodosError> { readonly GetTodos._tag: "GetTodos"
_tag: "GetTodos"}
// Create a tagged constructor for GetTodos requestsconst 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.
tagged<interface GetTodos
GetTodos>("GetTodos")
// Define a request to fetch a User by ID which might// fail with a GetUserErrorinterface 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.
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 requestsconst 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.
tagged<interface GetUserById
GetUserById>("GetUserById")
// Define a request to send an email which might// fail with a SendEmailErrorinterface 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.
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 requestsconst 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.
tagged<interface SendEmail
SendEmail>("SendEmail")
// ------------------------------// Resolvers With Context// ------------------------------
class class HttpService
HttpService extends import Context
Context.const Tag: <"HttpService">(id: "HttpService") => <Self, Shape>() => Context.TagClass<Self, "HttpService", Shape>
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.
fromEffect((_: GetTodos
_: interface GetTodos
GetTodos) => import Effect
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
andThen(class HttpService
HttpService, (http: { fetch: typeof fetch;}
http) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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>
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
Effect, import Context
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>
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>
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>
TaggedError("SendEmailError")<{}> {}
// ------------------------------// Requests// ------------------------------
29 collapsed lines
// Define a request to get multiple Todo items which might// fail with a GetTodosErrorinterface 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.
Request<interface Array<T>
Array<interface Todo
Todo>, class GetTodosError
GetTodosError> { readonly GetTodos._tag: "GetTodos"
_tag: "GetTodos"}
// Create a tagged constructor for GetTodos requestsconst 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.
tagged<interface GetTodos
GetTodos>("GetTodos")
// Define a request to fetch a User by ID which might// fail with a GetUserErrorinterface 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.
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 requestsconst 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.
tagged<interface GetUserById
GetUserById>("GetUserById")
// Define a request to send an email which might// fail with a SendEmailErrorinterface 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.
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 requestsconst 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.
tagged<interface SendEmail
SendEmail>("SendEmail")
// ------------------------------// Resolvers With Context// ------------------------------
21 collapsed lines
class class HttpService
HttpService extends import Context
Context.const Tag: <"HttpService">(id: "HttpService") => <Self, Shape>() => Context.TagClass<Self, "HttpService", Shape>
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.
fromEffect((_: GetTodos
_: interface GetTodos
GetTodos) => import Effect
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
andThen(class HttpService
HttpService, (http: { fetch: typeof fetch;}
http) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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>
contextFromServices(class HttpService
HttpService) )
// ------------------------------// Layers// ------------------------------
class class TodosService
TodosService extends import Context
Context.const Tag: <"TodosService">(id: "TodosService") => <Self, Shape>() => Context.TagClass<Self, "TodosService", Shape>
Tag("TodosService")< class TodosService
TodosService, { getTodos: Effect.Effect<Todo[], GetTodosError, never>
getTodos: import Effect
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.
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.
effect( class TodosService
TodosService, import Effect
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}`})
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.
fromEffect((_: GetTodos
_: interface GetTodos
GetTodos) => import Effect
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:
- If you don't provide a
catch function, the error is caught and the
effect fails with an UnknownException.
- 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)
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.
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
Effect.const request: <RequestResolver.RequestResolver<GetTodos, never>, GetTodos>(self: GetTodos, dataSource: RequestResolver.RequestResolver<GetTodos, never>) => Effect.Effect<Todo[], GetTodosError, never> (+1 overload)
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
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.
Effect< interface Array<T>
Array<interface Todo
Todo>, class GetTodosError
GetTodosError, class TodosService
TodosService> = import Effect
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))// orconst transformedEffect = Effect.andThen(myEffect, anotherEffect)// orconst 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 amountconst 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 databaseconst fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMapconst result1 = pipe( fetchTransactionAmount, Effect.map((amount) => amount * 2), Effect.flatMap((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result1).then(console.log)// Output: 190
// Using Effect.andThenconst result2 = pipe( fetchTransactionAmount, Effect.andThen((amount) => amount * 2), Effect.andThen((amount) => applyDiscount(amount, 5)))
Effect.runPromise(result2).then(console.log)// Output: 190
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))这种方法提供对缓存行为的细粒度控制,允许您根据应用程序不同部分的特定需求优化性能。