import { CoordinationAbortError, CoordinationTimeoutError } from "../errors.ts" /** * @description 表示等待节点上与 timeout / abort 绑定相关的内部状态。 */ export interface InternalWaitConstraintsEntry { timeoutId: ReturnType | undefined abortSignal: AbortSignal | undefined abortListener: (() => void) | undefined } /** * @description 表示等待约束的输入参数。 */ export interface InternalWaitConstraintsOptions { timeout?: number | undefined abortSignal?: AbortSignal | undefined } /** * @description 表示将等待约束绑定到某个节点时需要提供的回调集合。 */ export interface InternalWaitConstraintBindings { abortOperation: string timeoutOperation: string onAbort: (error: CoordinationAbortError) => void onTimeout: (error: CoordinationTimeoutError) => void } /** * @description 断言 timeout 参数是合法的非负有限数值。 */ export const internalAssertTimeoutOption = ( operation: string, timeout: number | undefined, ): void => { if (timeout === undefined) { return } if (Number.isFinite(timeout) === false || timeout < 0) { throw new RangeError(`${operation} timeout must be a finite number greater than or equal to 0.`) } } /** * @description 如果传入的 `AbortSignal` 已经处于中止状态,则立即抛出中止错误。 */ export const internalThrowIfAborted = ( operation: string, abortSignal: AbortSignal | undefined, ): void => { if (abortSignal?.aborted === true) { throw new CoordinationAbortError(operation, abortSignal.reason) } } /** * @description 清理已经绑定到等待节点上的超时器与中止监听器。 * * 该函数应当在节点成功结算或失败结算时都被调用,以避免悬挂的定时器与事件监听器泄漏。 */ export const internalCleanupWaitConstraints = (entry: InternalWaitConstraintsEntry): void => { if (entry.timeoutId !== undefined) { clearTimeout(entry.timeoutId) entry.timeoutId = undefined } if (entry.abortSignal !== undefined && entry.abortListener !== undefined) { entry.abortSignal.removeEventListener("abort", entry.abortListener) entry.abortSignal = undefined entry.abortListener = undefined } } /** * @description 把 timeout 与 abort 约束绑定到某个等待节点。 * * 绑定本身只负责注册外部约束,真正如何让节点失败由调用方通过 `bindings` 中的回调决定。 */ export const internalBindWaitConstraints = ( entry: InternalWaitConstraintsEntry, options: InternalWaitConstraintsOptions, bindings: InternalWaitConstraintBindings, ): void => { const { abortSignal, timeout } = options if (abortSignal !== undefined) { entry.abortSignal = abortSignal entry.abortListener = (): void => { bindings.onAbort(new CoordinationAbortError(bindings.abortOperation, abortSignal.reason)) } abortSignal.addEventListener("abort", entry.abortListener, { once: true }) } if (timeout !== undefined) { entry.timeoutId = setTimeout(() => { bindings.onTimeout(new CoordinationTimeoutError(bindings.timeoutOperation, timeout)) }, timeout) } }