超时
用 Effect 为操作设置时间限制,确保任务在指定时长内完成,并自定义超时时的行为。
在编程中,经常会遇到需要花一些时间才能完成的任务。我们往往希望给这些任务施加一个愿意等待的时长上限。Effect.timeout 函数可以为某个操作加上时间约束,确保它不会无限期地运行下去。
基本用法
timeout
Effect.timeout 函数接收一个 Duration 参数,用来为某个操作设定时间限制。如果该操作超出了这个限制,就会触发 TimeoutException,表示发生了超时。
示例(设置超时)
这里,任务在超时时长内完成,因此结果被成功返回。
import { Effect } from "effect"
const task = Effect.gen(function* () {
console.log("Start processing...")
yield* Effect.sleep("2 seconds") // Simulates a delay in processing
console.log("Processing complete.")
return "Result"
})
// Sets a 3-second timeout for the task
const timedEffect = task.pipe(Effect.timeout("3 seconds"))
// Output will show that the task completes successfully
// as it falls within the timeout duration
Effect.runPromiseExit(timedEffect).then(console.log)
/*
Output:
Start processing...
Processing complete.
{ _id: 'Exit', _tag: 'Success', value: 'Result' }
*/
如果操作超出了指定的时长,就会抛出 TimeoutException:
import { Effect } from "effect"
const task = Effect.gen(function* () {
console.log("Start processing...")
yield* Effect.sleep("2 seconds") // Simulates a delay in processing
console.log("Processing complete.")
return "Result"
})
// Output will show a TimeoutException as the task takes longer
// than the specified timeout duration
const timedEffect = task.pipe(Effect.timeout("1 second"))
Effect.runPromiseExit(timedEffect).then(console.log)
/*
Output:
Start processing...
{
_id: 'Exit',
_tag: 'Failure',
cause: {
_id: 'Cause',
_tag: 'Fail',
failure: { _tag: 'TimeoutException' }
}
}
*/
timeoutOption
如果你希望更优雅地处理超时,可以考虑使用 Effect.timeoutOption。这个函数把超时当作普通结果来处理,并将结果包装进一个 Option 中。
示例(把超时当作 Option 处理)
在这个示例中,第一个任务成功完成,而第二个任务超时了。超时任务的结果在 Option 类型中表示为 None。
import { Effect } from "effect"
const task = Effect.gen(function* () {
console.log("Start processing...")
yield* Effect.sleep("2 seconds") // Simulates a delay in processing
console.log("Processing complete.")
return "Result"
})
const timedOutEffect = Effect.all([
task.pipe(Effect.timeoutOption("3 seconds")),
task.pipe(Effect.timeoutOption("1 second")),
])
Effect.runPromise(timedOutEffect).then(console.log)
/*
Output:
Start processing...
Processing complete.
Start processing...
[
{ _id: 'Option', _tag: 'Some', value: 'Result' },
{ _id: 'Option', _tag: 'None' }
]
*/
处理超时
当某个操作没有在指定的时长内结束,Effect.timeout 的行为取决于该操作是否「不可中断」(uninterruptible)。
不可中断的 effect 指的是:一旦启动,超时机制就无法在执行过程中直接将其停止。 这可能是因为该 effect 内部的操作必须运行到完成,以避免让系统处于不一致的状态。
-
可中断的操作:如果操作可以被中断,那么一旦达到超时阈值,它就会立即被终止,并产生一个
TimeoutException。import { Effect } from "effect" const task = Effect.gen(function* () { console.log("Start processing...") yield* Effect.sleep("2 seconds") // Simulates a delay in processing console.log("Processing complete.") return "Result" }) const timedEffect = task.pipe(Effect.timeout("1 second")) Effect.runPromiseExit(timedEffect).then(console.log) /* Output: Start processing... { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: { _tag: 'TimeoutException' } } } */ -
不可中断的操作:如果操作不可中断,它会一直继续到完成为止,然后才会判定
TimeoutException。import { Effect } from "effect" const task = Effect.gen(function* () { console.log("Start processing...") yield* Effect.sleep("2 seconds") // Simulates a delay in processing console.log("Processing complete.") return "Result" }) const timedEffect = task.pipe( Effect.uninterruptible, Effect.timeout("1 second"), ) // Outputs a TimeoutException after the task completes, // because the task is uninterruptible Effect.runPromiseExit(timedEffect).then(console.log) /* Output: Start processing... Processing complete. { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: { _tag: 'TimeoutException' } } } */
超时时断开连接
Effect.disconnect 函数提供了一种更灵活的方式,用来处理不可中断 effect 中的超时。它允许不可中断的 effect 在后台继续完成,而主控制流则像已经发生超时一样继续向下执行。
两者的区别如下:
不使用 Effect.disconnect 时:
- 不可中断的 effect 会忽略超时,继续执行直到完成,之后才会判定超时错误。
- 这可能导致超时条件的识别被延迟,因为系统必须等待该 effect 完成。
使用 Effect.disconnect 时:
- 允许不可中断的 effect 在后台继续运行,独立于主控制流。
- 主控制流会立即识别出超时,并带着超时错误或替代逻辑继续执行,而不必等待该 effect 完成。
- 当该 effect 中的操作虽然被标记为不可中断,却不需要阻塞程序继续执行时,这种方式尤其有用。
示例(运行不可中断任务并设置超时,同时在后台完成)
考虑这样一个场景:启动了一个长时间运行的数据处理任务,而你希望即使数据处理耗时过长,系统也能保持响应:
import { Effect } from "effect"
const longRunningTask = Effect.gen(function* () {
console.log("Start heavy processing...")
yield* Effect.sleep("5 seconds") // Simulate a long process
console.log("Heavy processing done.")
return "Data processed"
})
const timedEffect = longRunningTask.pipe(
Effect.uninterruptible,
// Allows the task to finish in the background if it times out
Effect.disconnect,
Effect.timeout("1 second"),
)
Effect.runPromiseExit(timedEffect).then(console.log)
/*
Output:
Start heavy processing...
{
_id: 'Exit',
_tag: 'Failure',
cause: {
_id: 'Cause',
_tag: 'Fail',
failure: { _tag: 'TimeoutException' }
}
}
Heavy processing done.
*/
在这个示例中,系统在一秒后检测到了超时,但长时间运行的任务会在后台继续执行并完成,不会阻塞程序的流程。
自定义超时行为
除了基础的 Effect.timeout 函数之外,还有若干变体可供使用,让你能够自定义发生超时时的行为。
timeoutFail
Effect.timeoutFail 函数允许你在发生超时时产生一个特定的错误。
示例(自定义超时错误)
import { Effect, Data } from "effect"
const task = Effect.gen(function* () {
console.log("Start processing...")
yield* Effect.sleep("2 seconds") // Simulates a delay in processing
console.log("Processing complete.")
return "Result"
})
class MyTimeoutError extends Data.TaggedError("MyTimeoutError")<{}> {}
const program = task.pipe(
Effect.timeoutFail({
duration: "1 second",
onTimeout: () => new MyTimeoutError(), // Custom timeout error
}),
)
Effect.runPromiseExit(program).then(console.log)
/*
Output:
Start processing...
{
_id: 'Exit',
_tag: 'Failure',
cause: {
_id: 'Cause',
_tag: 'Fail',
failure: MyTimeoutError { _tag: 'MyTimeoutError' }
}
}
*/
timeoutFailCause
Effect.timeoutFailCause 让你可以定义一个在发生超时时抛出的特定 defect。当你希望把超时当作代码中的异常情况来处理时,这会很有帮助。
示例(超时时抛出自定义 defect)
import { Effect, Cause } from "effect"
const task = Effect.gen(function* () {
console.log("Start processing...")
yield* Effect.sleep("2 seconds") // Simulates a delay in processing
console.log("Processing complete.")
return "Result"
})
const program = task.pipe(
Effect.timeoutFailCause({
duration: "1 second",
onTimeout: () => Cause.die("Timed out!"), // Custom defect for timeout
}),
)
Effect.runPromiseExit(program).then(console.log)
/*
Output:
Start processing...
{
_id: 'Exit',
_tag: 'Failure',
cause: { _id: 'Cause', _tag: 'Die', defect: 'Timed out!' }
}
*/
timeoutTo
与 Effect.timeout 相比,Effect.timeoutTo 提供了更大的灵活性,允许你分别为成功和超时的操作定义不同的结果。当你希望根据操作是否按时完成来自定义结果时,这会很有用。
示例(用 Either 处理成功与超时)
import { Effect, Either } from "effect"
const task = Effect.gen(function* () {
console.log("Start processing...")
yield* Effect.sleep("2 seconds") // Simulates a delay in processing
console.log("Processing complete.")
return "Result"
})
const program = task.pipe(
Effect.timeoutTo({
duration: "1 second",
onSuccess: (result): Either.Either<string, string> => Either.right(result),
onTimeout: (): Either.Either<string, string> => Either.left("Timed out!"),
}),
)
Effect.runPromise(program).then(console.log)
/*
Output:
Start processing...
{
_id: "Either",
_tag: "Left",
left: "Timed out!"
}
*/