/** * Generic retry engine for QQ Bot API requests. * * Replaces the three separate retry implementations in the old `api.ts`: * - `apiRequestWithRetry` (upload retry with exponential backoff) * - `partFinishWithRetry` (part-finish retry + persistent retry on specific biz codes) * - `completeUploadWithRetry` (unconditional retry for complete-upload) * * All three patterns are expressed as a single `withRetry` function * parameterized by `RetryPolicy` and optional `PersistentRetryPolicy`.
*/
import type { EngineLogger } from "../types.js"; import { formatErrorMessage } from "../utils/format.js";
/** Standard retry policy with exponential or fixed backoff. */
export interface RetryPolicy { /** Maximum retry attempts (excluding the initial attempt). */
maxRetries: number; /** Base delay in milliseconds. */
baseDelayMs: number; /** Backoff strategy. */
backoff: "exponential" | "fixed"; /** * Predicate to decide whether an error is retryable. * Return `false` to immediately rethrow. * Defaults to always-retry when omitted.
*/
shouldRetry?: (error: Error, attempt: number) => boolean;
}
/** * Persistent retry policy for specific business error codes. * * When `shouldPersistRetry` returns true, the engine switches from * the standard retry loop into a tight fixed-interval loop bounded * only by the total timeout.
*/
export interface PersistentRetryPolicy { /** Total timeout in milliseconds for the persistent retry loop. */
timeoutMs: number; /** Fixed interval between retries in milliseconds. */
intervalMs: number; /** Predicate to decide whether an error triggers persistent retry. */
shouldPersistRetry: (error: Error) => boolean;
}
/** * Execute an async operation with configurable retry semantics. * * @param fn - The async operation to retry. * @param policy - Standard retry configuration. * @param persistentPolicy - Optional persistent retry for specific error codes. * @param logger - Optional logger for retry diagnostics. * @returns The result of the first successful invocation.
*/
export async function withRetry<T>(
fn: () => Promise<T>,
policy: RetryPolicy,
persistentPolicy?: PersistentRetryPolicy,
logger?: EngineLogger,
): Promise<T> {
let lastError: Error | null = null;
/** * Persistent retry loop: fixed-interval retries bounded by a total timeout. * * Used for `upload_part_finish` when the server returns specific business * error codes indicating the backend is still processing.
*/
async function persistentRetryLoop<T>(
fn: () => Promise<T>,
policy: PersistentRetryPolicy,
logger?: EngineLogger,
): Promise<T> { const deadline = Date.now() + policy.timeoutMs;
let attempt = 0;
let lastError: Error | null = null;
while (Date.now() < deadline) { try { const result = await fn();
logger?.debug?.(`[qqbot:retry] Persistent retry succeeded after ${attempt} retries`); return result;
} catch (err) {
lastError = err instanceof Error ? err : new Error(formatErrorMessage(err));
// If the error is no longer retryable, abort immediately. if (!policy.shouldPersistRetry(lastError)) {
logger?.error?.(`[qqbot:retry] Persistent retry: error is no longer retryable, aborting`); throw lastError;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.