gongxiangp2/lib/task-detail-upload-status.ts
2026-08-02 01:04:11 +08:00

82 lines
2.3 KiB
TypeScript

export type TaskDetailUploadJobStatus = {
jobId: string
materialId?: string
status: 'queued' | 'processing' | 'completed' | 'failed'
stage?: string
progress?: number
error?: string
taskNo?: string
}
export class TaskDetailUploadJobError extends Error {
readonly jobStatus?: TaskDetailUploadJobStatus['status']
constructor(message: string, jobStatus?: TaskDetailUploadJobStatus['status']) {
super(message)
this.name = 'TaskDetailUploadJobError'
this.jobStatus = jobStatus
}
}
type PollOptions = {
signal?: AbortSignal
timeoutMs?: number
onStatus?: (status: TaskDetailUploadJobStatus) => void
}
function wait(delayMs: number, signal?: AbortSignal) {
return new Promise<void>((resolve, reject) => {
if (signal?.aborted) {
reject(new DOMException('上传已取消', 'AbortError'))
return
}
let timer: ReturnType<typeof setTimeout>
function cleanup() {
signal?.removeEventListener('abort', handleAbort)
}
function handleResolve() {
cleanup()
resolve()
}
function handleAbort() {
clearTimeout(timer)
cleanup()
reject(new DOMException('上传已取消', 'AbortError'))
}
timer = setTimeout(handleResolve, delayMs)
signal?.addEventListener('abort', handleAbort, { once: true })
})
}
export async function pollTaskDetailUploadJob(
jobId: string,
options: PollOptions = {},
): Promise<TaskDetailUploadJobStatus> {
const deadline = Date.now() + (options.timeoutMs ?? 30 * 60 * 1000)
let delayMs = 800
while (Date.now() < deadline) {
const response = await fetch(
`/api/materials/upload/product-package/status?jobId=${encodeURIComponent(jobId)}`,
{ signal: options.signal },
)
const payload = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(payload?.error || '查询后台处理状态失败')
}
const status = (payload?.data || payload) as TaskDetailUploadJobStatus
options.onStatus?.(status)
if (status.status === 'completed') return status
if (status.status === 'failed') {
throw new TaskDetailUploadJobError(status.error || '后台处理失败', status.status)
}
await wait(delayMs, options.signal)
delayMs = Math.min(3_000, Math.round(delayMs * 1.25))
}
throw new Error('后台处理超时,请稍后到素材详情查看处理结果')
}