yikart/AiToEarn · error · Error
Download failed: ${response.status}
Error message
Download failed: ${response.status} What it means
fetchWithProgress downloads a URL with fetch and throws `Download failed: ${response.status}` when response.ok is false (any non-2xx HTTP status). It surfaces the raw HTTP status because download failures are almost always server-side rejections.
Source
Thrown at project/aitoearn-web/src/utils/download.ts:18
/**
* download.ts - 下载工具函数
* 提供带进度回调的文件下载功能
*/
/**
* 带进度回调的 fetch 下载
* 通过 ReadableStream 读取响应体,实时计算下载百分比
* 无 Content-Length 时降级为无进度下载(直接 blob)
*/
export async function fetchWithProgress(
url: string,
onProgress?: (progress: number) => void,
init?: RequestInit,
): Promise<Blob> {
const response = await fetch(url, init ?? { mode: 'no-cors' })
if (!response.ok) {
throw new Error(`Download failed: ${response.status}`)
}
const contentLength = response.headers.get('Content-Length')
// 无 Content-Length 或无 body,降级为直接 blob
if (!contentLength || !response.body) {
const blob = await response.blob()
onProgress?.(100)
return blob
}
const total = Number.parseInt(contentLength, 10)
let loaded = 0
const reader = response.body.getReader()
const chunks: Uint8Array[] = []
while (true) {
const { done, value } = await reader.read()
if (done)View on GitHub (pinned to d3aa8bea5b)
Solutions
- Log response.status and re-fetch a fresh (re-signed) URL for the asset
- Retry with backoff for 5xx; do not retry 4xx client errors
- Check that the URL is reachable directly (curl -I) and that CORS/Referer rules allow the request
- If using mode:'no-cors', note the response is opaque — prefer a proper CORS-enabled request so status is readable
Example fix
// before
const blob = await fetchWithProgress(url)
// after
try {
const blob = await fetchWithProgress(url)
} catch (e) {
if (String(e.message).includes('Download failed: 4')) {
url = await refreshSignedUrl(assetId) // get a fresh URL
blob = await fetchWithProgress(url)
} else throw e
} Defensive patterns
Strategy: retry
Validate before calling
async function isUrlReachable(url: string): Promise<boolean> {
try { const r = await fetch(url, { method: 'HEAD' }); return r.ok } catch { return false }
} Type guard
function isOkResponse(r: Response): boolean { return r.ok } Try / catch
try {
const blob = await fetchWithProgress(url, onProgress)
} catch (e) {
const m = /Download failed: (\d+)/.exec(e.message)
if (m && Number(m[1]) >= 500) return retryWithBackoff(() => fetchWithProgress(url, onProgress))
if (m && Number(m[1]) === 404) throw new Error('资源不存在,请刷新后重试')
throw e
} Prevention
- Use freshly signed, short-lived URLs right before download
- Retry 5xx/429 with exponential backoff; fail fast on 4xx
- Verify CDN/CORS settings allow cross-origin downloads from the app domain
When it happens
Trigger: Any fetch in fetchWithProgress (project/aitoearn-web/src/utils/download.ts) receiving 4xx/5xx — expired/signed-out media URL, 403 from hotlink/CORS policy, 404 for removed asset, 5xx from the asset server.
Common situations: Downloading media whose signed URL expired, CDN or origin returning 403/404, corporate proxy intercepting with an error page, or calling with mode:'no-cors' against a server that rejects opaque requests.
Related errors
- Failed to fetch image: ${response.status} ${response.statusT
- S3DownloadFileFailed
- ResponseCode.S3DownloadFileFailed
- ResponseCode.S3DownloadFileFailed
- Failed to fetch ${url}: ${response.status}
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/6551c74ef62a202a.
Report an issue: GitHub.