示例
探索在 Effect 中处理调度、重试、超时以及周期性任务执行的实用示例。
以下示例展示了使用 Effect 处理超时、重试和周期性执行的不同方式。每个场景都确保应用保持响应能力、能从失败中恢复,同时动态适应各种情况。
处理 API 调用的超时与重试
在调用第三方 API 时,通常需要设置超时并实现重试机制,以应对临时性失败。在本例中,API 调用在失败时最多重试两次;如果耗时超过 4 秒,就会被中断。
示例(带超时的 API 调用重试)
import { Console, Effect } from "effect"
// Function to make the API call
const getJson = (url: string) =>
Effect.tryPromise(() =>
fetch(url).then((res) => {
if (!res.ok) {
console.log("error")
throw new Error(res.statusText)
}
console.log("ok")
return res.json() as unknown
}),
)
// Program that retries the API call twice, times out after 4 seconds,
// and logs errors
const program = (url: string) =>
getJson(url).pipe(
Effect.retry({ times: 2 }),
Effect.timeout("4 seconds"),
Effect.catchAll(Console.error),
)
// Test case: successful API response
Effect.runFork(program("https://dummyjson.com/products/1?delay=1000"))
/*
Output:
ok
*/
// Test case: API call exceeding timeout limit
Effect.runFork(program("https://dummyjson.com/products/1?delay=5000"))
/*
Output:
TimeoutException: Operation timed out before the specified duration of '4s' elapsed
*/
// Test case: API returning an error response
Effect.runFork(program("https://dummyjson.com/auth/products/1?delay=500"))
/*
Output:
error
error
error
UnknownException: An unknown error occurred
*/
根据特定错误重试 API 调用
有时,只应针对某些错误情况进行重试。例如,如果 API 调用以 401 Unauthorized 响应失败,重试可能是合理的;而 404 Not Found 错误则不应触发重试。
示例(仅针对特定错误码重试)
import { Console, Effect, Data } from "effect"
// Custom error class for handling status codes
class Err extends Data.TaggedError("Err")<{
readonly message: string
readonly status: number
}> {}
// Function to make the API call
const getJson = (url: string) =>
Effect.tryPromise({
try: () =>
fetch(url).then((res) => {
if (!res.ok) {
console.log(res.status)
throw new Err({ message: res.statusText, status: res.status })
}
return res.json() as unknown
}),
catch: (e) => e as Err,
})
// Program that retries only when the error status is 401 (Unauthorized)
const program = (url: string) =>
getJson(url).pipe(
Effect.retry({ while: (err) => err.status === 401 }),
Effect.catchAll(Console.error),
)
// Test case: API returns 401 (triggers multiple retries)
Effect.runFork(program("https://dummyjson.com/auth/products/1?delay=1000"))
/*
Output:
401
401
401
401
...
*/
// Test case: API returns 404 (no retries)
Effect.runFork(program("https://dummyjson.com/-"))
/*
Output:
404
Err [Error]: Not Found
*/
根据错误信息动态调整重试延迟
某些 API 错误(例如 429 Too Many Requests)会带有 Retry-After 响应头,其中指定了重试前需要等待的时长。我们可以根据该值动态调整重试间隔,而不是使用固定延迟。
示例(使用 Retry-After 响应头决定重试延迟)
这种做法让重试延迟能够根据服务器的响应动态调整,在遵循所提供的 Retry-After 值的同时避免不必要的重试。
import { Duration, Effect, Schedule, Data } from "effect"
// Custom error class representing a "Too Many Requests" response
class TooManyRequestsError extends Data.TaggedError("TooManyRequestsError")<{
readonly retryAfter: number
}> {}
let n = 1
const request = Effect.gen(function* () {
// Simulate failing a particular number of times
if (n < 3) {
const retryAfter = n * 500
console.log(`Attempt #${n++}, retry after ${retryAfter} millis...`)
// Simulate retrieving the retry-after header
return yield* Effect.fail(new TooManyRequestsError({ retryAfter }))
}
console.log("Done")
return "some result"
})
// Retry policy that extracts the retry delay from the error
const policy = Schedule.identity<TooManyRequestsError>().pipe(
Schedule.addDelay((error) =>
error._tag === "TooManyRequestsError"
? // Wait for the specified retry-after duration
Duration.millis(error.retryAfter)
: Duration.zero,
),
// Limit retries to 5 attempts
Schedule.intersect(Schedule.recurs(5)),
)
const program = request.pipe(Effect.retry(policy))
Effect.runFork(program)
/*
Output:
Attempt #1, retry after 500 millis...
Attempt #2, retry after 1000 millis...
Done
*/
运行周期性任务直到另一个任务完成
有些情况下,我们需要按固定间隔重复执行某个动作,直到另一个耗时更长的任务完成。这种模式常见于轮询机制或周期性日志记录。
示例(运行定时任务直到完成)
import { Effect, Console, Schedule } from "effect"
// Define a long-running effect
// (e.g., a task that takes 5 seconds to complete)
const longRunningEffect = Console.log("done").pipe(Effect.delay("5 seconds"))
// Define an action to run periodically
const action = Console.log("action...")
// Define a fixed interval schedule
const schedule = Schedule.fixed("1.5 seconds")
// Run the action repeatedly until the long-running task completes
const program = Effect.race(Effect.repeat(action, schedule), longRunningEffect)
Effect.runPromise(program)
/*
Output:
action...
action...
action...
action...
done
*/