{"record":{"id":"8b2091d0494950e9","repo":"yikart/AiToEarn","slug":"error-8b2091","errorCode":null,"errorMessage":"上传已取消","messagePattern":"上传已取消","errorType":"exception","errorClass":"DOMException","httpStatus":null,"severity":"info","filePath":"project/aitoearn-web/src/utils/media.ts","lineNumber":159,"sourceCode":"  if (typeof File === 'undefined')\n    return null\n\n  if (file instanceof File)\n    return file\n\n  return new File([file], getUploadImageFileName(file), {\n    type: getCompressionFileType(file.type),\n    lastModified: Date.now(),\n  })\n}\n\n/** 上传前优化图片：超过 1MB 的 jpeg/png/webp 会限制最长边并尽量压缩到 1MB 内 */\nexport async function optimizeImageForUpload(file: File | Blob, options?: OptimizeImageForUploadOptions) {\n  if (!shouldOptimizeImageForUpload(file) || typeof window === 'undefined')\n    return file\n\n  if (options?.signal?.aborted)\n    throw new DOMException('上传已取消', 'AbortError')\n\n  const compressionFile = toImageCompressionFile(file)\n  if (!compressionFile)\n    return file\n\n  try {\n    const { default: imageCompression } = await import('browser-image-compression')\n    const compressionOptions: ImageCompressionOptions = {\n      maxSizeMB: IMAGE_UPLOAD_MAX_SIZE_MB,\n      maxWidthOrHeight: IMAGE_UPLOAD_MAX_WIDTH_OR_HEIGHT,\n      initialQuality: IMAGE_UPLOAD_INITIAL_QUALITY,\n      useWebWorker: false,\n      fileType: getCompressionFileType(compressionFile.type),\n      signal: options?.signal,\n    }\n    const compressedFile = await imageCompression(compressionFile, compressionOptions)\n\n    return compressedFile.size < file.size ? compressedFile : file","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-web/src/utils/media.ts#L141-L177","documentation":"optimizeImageForUpload honors an AbortSignal so callers can cancel pre-upload image optimization. If options.signal.aborted is already true when the function starts, it throws DOMException('上传已取消', 'AbortError') instead of doing wasted compression work.","triggerScenarios":"Calling optimizeImageForUpload (directly or via uploadFile) with an AbortSignal that was aborted before the call — e.g. user cancelled the upload, component unmounted, or a timeout fired before optimization began.","commonSituations":"User removes a file from an upload queue while it is pending, a React effect aborts on unmount, or an overall upload deadline aborts the controller at the optimization stage.","solutions":["Treat DOMException with name 'AbortError' as an expected cancellation: swallow it or update UI to 'cancelled', don't show it as an error","Check signal.aborted in the caller before invoking uploadFile if cancellation is possible","Create a fresh AbortController for each upload instead of reusing an already-aborted one"],"exampleFix":"// before\nawait uploadFile(file, { signal: controller.signal }) // throws if already aborted\n// after\nif (!controller.signal.aborted) {\n  await uploadFile(file, { signal: controller.signal })\n}","handlingStrategy":"try-catch","validationCode":"function canStartUpload(signal?: AbortSignal): boolean {\n  return !signal?.aborted\n}","typeGuard":"function isAbortError(e: unknown): e is DOMException {\n  return e instanceof DOMException && e.name === 'AbortError'\n}","tryCatchPattern":"try {\n  await uploadFile(file, { signal })\n} catch (e) {\n  if (isAbortError(e)) { markUploadCancelled(file); return } // expected cancellation\n  throw e\n}","preventionTips":["Always check signal.aborted before starting an upload","Use a dedicated AbortController per upload, not a shared/reused one","In UI, distinguish 'cancelled' from 'failed' states"],"tags":["abort","upload","cancellation"],"backgroundTag":"abort-error-upload-cancelled","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}