错误累积
学习在 Effect 工作流中有效地管理错误:掌握顺序执行、错误累积与结果划分的工具。
诸如 Effect.zip、Effect.all 和 Effect.forEach 这类顺序组合子,在错误管理上采用「快速失败」(fail fast)策略。这意味着它们一旦遇到第一个错误,就会立即停止并返回。
下面是一个使用 Effect.zip 的示例,它会在第一个失败处停止,并且只显示第一个错误:
示例(使用 Effect.zip 快速失败)
import { Effect, Console } from "effect"
const task1 = Console.log("task1").pipe(Effect.as(1))
const task2 = Effect.fail("Oh uh!").pipe(Effect.as(2))
const task3 = Console.log("task2").pipe(Effect.as(3))
const task4 = Effect.fail("Oh no!").pipe(Effect.as(4))
const program = task1.pipe(
Effect.zip(task2),
Effect.zip(task3),
Effect.zip(task4),
)
Effect.runPromise(program).then(console.log, console.error)
/*
Output:
task1
(FiberFailure) Error: Oh uh!
*/
Effect.forEach 函数的行为与之类似。它会把一个产生 effect 的操作应用到集合中的每个元素,但遇到第一个错误时就会停止:
示例(使用 Effect.forEach 快速失败)
import { Effect, Console } from "effect"
const program = Effect.forEach([1, 2, 3, 4, 5], (n) => {
if (n < 4) {
return Console.log(`item ${n}`).pipe(Effect.as(n))
} else {
return Effect.fail(`${n} is not less that 4`)
}
})
Effect.runPromise(program).then(console.log, console.error)
/*
Output:
item 1
item 2
item 3
(FiberFailure) Error: 4 is not less that 4
*/
不过,有些场景下你可能希望收集所有错误,而不是快速失败。在这些情况下,你可以使用那些同时累积成功与错误的函数。
validate
Effect.validate 函数与 Effect.zip 类似,但即使遇到错误,它也会继续组合各个 effect,从而同时累积成功与失败。
示例(校验并收集错误)
import { Effect, Console } from "effect"
const task1 = Console.log("task1").pipe(Effect.as(1))
const task2 = Effect.fail("Oh uh!").pipe(Effect.as(2))
const task3 = Console.log("task2").pipe(Effect.as(3))
const task4 = Effect.fail("Oh no!").pipe(Effect.as(4))
const program = task1.pipe(
Effect.validate(task2),
Effect.validate(task3),
Effect.validate(task4),
)
Effect.runPromiseExit(program).then(console.log)
/*
Output:
task1
task2
{
_id: 'Exit',
_tag: 'Failure',
cause: {
_id: 'Cause',
_tag: 'Sequential',
left: { _id: 'Cause', _tag: 'Fail', failure: 'Oh uh!' },
right: { _id: 'Cause', _tag: 'Fail', failure: 'Oh no!' }
}
}
*/
validateAll
Effect.validateAll 函数与 Effect.forEach 函数类似。它使用所提供的、会产生 effect 的操作来转换集合中的所有元素,但它会把所有错误收集到错误通道中,同时把成功值收集到成功通道中。
import { Effect, Console } from "effect"
// ┌─── Effect<number[], string[], never>
// ▼
const program = Effect.validateAll([1, 2, 3, 4, 5], (n) => {
if (n < 4) {
return Console.log(`item ${n}`).pipe(Effect.as(n))
} else {
return Effect.fail(`${n} is not less that 4`)
}
})
Effect.runPromiseExit(program).then(console.log)
/*
Output:
item 1
item 2
item 3
{
_id: 'Exit',
_tag: 'Failure',
cause: {
_id: 'Cause',
_tag: 'Fail',
failure: [ '4 is not less that 4', '5 is not less that 4' ]
}
}
*/
注意,这个函数是有损的:只要存在任何错误,所有成功值都会丢失。如果你需要同时保留成功与失败, 可以考虑使用 Effect.partition。
validateFirst
Effect.validateFirst 函数与 Effect.validateAll 类似,但它会返回第一个成功的结果;如果没有任何元素成功,则返回所有错误。
示例(返回第一个成功结果)
import { Effect, Console } from "effect"
// ┌─── Effect<number, string[], never>
// ▼
const program = Effect.validateFirst([1, 2, 3, 4, 5], (n) => {
if (n < 4) {
return Effect.fail(`${n} is not less that 4`)
} else {
return Console.log(`item ${n}`).pipe(Effect.as(n))
}
})
Effect.runPromise(program).then(console.log, console.error)
/*
Output:
item 4
4
*/
注意,Effect.validateFirst 返回的成功类型是单个 number,而不像 Effect.validateAll 那样返回结果数组。
partition
Effect.partition 函数会处理一个可迭代对象,并把一个会产生 effect 的函数应用到每个元素上。它返回一个元组,其中第一部分包含所有失败,第二部分包含所有成功。
示例(划分成功与失败)
import { Effect } from "effect"
// ┌─── Effect<[string[], number[]], never, never>
// ▼
const program = Effect.partition([0, 1, 2, 3, 4], (n) => {
if (n % 2 === 0) {
return Effect.succeed(n)
} else {
return Effect.fail(`${n} is not even`)
}
})
Effect.runPromise(program).then(console.log, console.error)
/*
Output:
[ [ '1 is not even', '3 is not even' ], [ 0, 2, 4 ] ]
*/
这个操作符是一个不会失败的 effect(unexceptional effect),也就是说其错误通道类型为 never。失败会被收集而不会中断该 effect,因此整个操作会执行完成,并同时返回错误与成功。