28-图片上传卡住-用任务状态机和可取消重试链路处理
第28篇图片上传卡住用任务状态机和可取消重试链路处理摘要图片上传卡住时继续给按钮加 loading 通常解决不了。上传有文件校验、网络状态、进度回调、取消、重试、成功写入业务记录等多个阶段。更稳的做法是把上传建成任务状态机页面只展示任务状态Service 负责启动、取消和重试长时间无进度时进入可恢复状态而不是让用户卡在一个转圈里。我处理过一个头像上传问题用户选图后按钮显示“上传中”进度一直停在 0%返回再进仍然不能重新选图。后来发现页面只保存了一个uploadingtrue没有记录任务 id、文件路径、进度更新时间和失败原因。网络断开或系统代理上传没有回调时页面就不知道该取消还是重试。这篇文章解决四个实际问题如何把上传拆成 pending、uploading、failed、done 等明确状态。如何处理长时间没有进度的卡住情况。如何让取消和重试都回到同一个任务模型。如何验证断网、慢网、大图和页面返回路径。上传卡住先看状态是否可解释一个uploading布尔值无法解释上传卡住。状态用户应该看到处理方式pending等待上传可以取消或开始uploading进度和取消入口监听进度更新时间stalled长时间无进度提供重试和取消failed明确失败原因允许重试done上传完成写入业务记录状态能解释页面才知道按钮该不该禁用、用户能不能返回、失败后是否可以重试。定义上传任务模型上传任务要带任务 id、文件信息、进度和最后一次进度时间。exporttypeUploadStatuspending|uploading|stalled|failed|done|canceledexportinterfaceImageUploadTask{taskId:stringlocalUri:stringfileName:stringsizeBytes:numberstatus:UploadStatus progress:numberlastProgressAt:numbererrorText:stringremoteUrl:string}exportfunctioncreateImageUploadTask(localUri:string,fileName:string,sizeBytes:number):ImageUploadTask{constnowDate.now()return{taskId:${now}_${fileName},localUri,fileName,sizeBytes,status:pending,progress:0,lastProgressAt:now,errorText:,remoteUrl:}}lastProgressAt是处理卡住的关键。没有它页面只能看到“还在上传”无法判断是否已经长时间无进度。上传前先做本地校验大图、空路径、类型不支持这些问题不要等上传接口返回才处理。exportclassImageUploadValidator{validate(task:ImageUploadTask):string{if(task.localUri.length0){return图片路径无效请重新选择}if(!task.fileName.endsWith(.jpg)!task.fileName.endsWith(.png)){return仅支持 JPG 或 PNG 图片}if(task.sizeBytes8*1024*1024){return图片超过 8MB请压缩后再上传}return}}校验失败时任务可以停在failed并给出明确原因。不要让用户看到一个永远不会成功的上传进度。Service 负责启动、进度和取消页面不直接调用上传 API。Service 把底层上传能力包装成任务状态更新。exportinterfaceUploadProgress{sentBytes:numbertotalBytes:number}exportinterfaceUploadTransport{start(task:ImageUploadTask,onProgress:(progress:UploadProgress)void):Promisestringcancel(taskId:string):Promisevoid}exportclassImageUploadService{constructor(privatereadonlytransport:UploadTransport,privatereadonlyvalidator:ImageUploadValidator){}asyncstart(task:ImageUploadTask,update:(task:ImageUploadTask)void):Promisevoid{consterrorthis.validator.validate(task)if(error.length0){update({...task,status:failed,errorText:error})return}update({...task,status:uploading,errorText:})constremoteUrlawaitthis.transport.start(task,(progress:UploadProgress){constpercentMath.floor(progress.sentBytes*100/progress.totalBytes)update({...task,status:uploading,progress:percent,lastProgressAt:Date.now()})})update({...task,status:done,progress:100,remoteUrl})}asynccancel(task:ImageUploadTask):Promisevoid{awaitthis.transport.cancel(task.taskId)}}这里的UploadTransport可以接系统上传下载能力也可以接项目已有网络层。文章重点是状态边界而不是把底层 API 写死在页面里。长时间无进度要变成可恢复状态卡住不是失败但必须可见、可取消、可重试。exportclassUploadWatchdog{constructor(privatereadonlystallMs:number15000){}markIfStalled(task:ImageUploadTask):ImageUploadTask{if(task.status!uploading){returntask}if(Date.now()-task.lastProgressAtthis.stallMs){returntask}return{...task,status:stalled,errorText:上传长时间没有进度请检查网络后重试}}}页面可以定时调用这段逻辑也可以在进度回调缺失时由 Service 触发。关键是不要让用户只看到一个无限转圈。页面按钮跟状态绑定上传页只展示任务状态和触发动作不拼接底层上传细节。Componentstruct ImageUploadPanel{Statetask:ImageUploadTask|nullnullprivateservice:ImageUploadServicecreateImageUploadService()privatecanRetry():boolean{returnthis.task?.statusfailed||this.task?.statusstalled}BuilderprivateActionButtons(){Row({space:12}){if(this.task?.statusuploading){Button(取消上传).onClick(()this.cancelUpload())}if(this.canRetry()){Button(重试).onClick(()this.retryUpload())}}}}这里没有在 Builder 里直接拼复杂判断。canRetry()返回安全布尔值按钮状态和任务状态一一对应。验证清单选择超过限制的大图确认不发起上传并给出原因。断网后上传确认进入 failed 或 stalled而不是无限 loading。上传中取消确认底层任务被取消页面可重新选择。慢网 0% 停留超过阈值确认出现重试入口。上传成功后写入业务记录再返回页面不丢 remoteUrl。连续选择两张图旧任务不能覆盖新任务状态。上传结果写入业务前要二次确认图片上传成功不等于业务保存成功。头像、课程封面、笔记图片通常还要把remoteUrl写入用户资料或课程记录。如果上传成功但业务写入失败也要给用户明确状态。阶段失败后状态用户能做什么本地校验失败failed重新选择图片上传中断stalled或failed重试或取消上传成功但业务保存失败done但未绑定业务重试保存引用用户取消canceled重新选择我会把“上传文件”和“绑定业务记录”拆开。这样上传成功后即使保存课程失败也不需要重新上传同一张图只需要重试绑定。给上传任务补一个绑定阶段exportinterfaceImageBindPayload{businessId:stringremoteUrl:stringusage:avatar|course_cover|note_image}exportclassImageBindService{constructor(privatereadonlyrepository:ImageReferenceRepository){}asyncbindUploadedImage(payload:ImageBindPayload):Promisevoid{if(payload.remoteUrl.length0){thrownewError(remote url is required)}awaitthis.repository.saveReference(payload)}}这段逻辑防止上传层和业务层互相越界。上传 Service 不知道图片最终挂在哪里业务绑定 Service 也不负责重新上传文件。排查时先看任务状态再看绑定结果。小结上传是一条任务链路图片上传卡住时不要只修按钮。把上传建成任务状态机记录进度更新时间Service 负责启动取消重试页面只展示状态。这样断网、慢网、大图、取消和重试都有明确位置用户不会被困在一个没有解释的 loading 里。(payload)}}这段逻辑防止上传层和业务层互相越界。上传 Service 不知道图片最终挂在哪里业务绑定 Service 也不负责重新上传文件。排查时先看任务状态再看绑定结果。 ## 小结上传是一条任务链路 图片上传卡住时不要只修按钮。把上传建成任务状态机记录进度更新时间Service 负责启动取消重试页面只展示状态。这样断网、慢网、大图、取消和重试都有明确位置用户不会被困在一个没有解释的 loading 里。

相关新闻

银河麒麟高级服务器操作系统V10SP2(ARM)下Mellanox网卡驱动编译与内核适配实战

银河麒麟高级服务器操作系统V10SP2(ARM)下Mellanox网卡驱动编译与内核适配实战

1. 环境准备与驱动包获取 在银河麒麟高级服务器操作系统V10SP2(ARM架构)上手动编译Mellanox网卡驱动前,需要确认三个关键要素: 操作系统版本 、 内核版本 和 网卡型号 。以实际生产环境为例,执行以下命令查看系…

2026/7/15 21:40:19阅读更多 →
C语言轻量级RSA库集成指南:三步实现嵌入式数据加密

C语言轻量级RSA库集成指南:三步实现嵌入式数据加密

1. 项目概述:为什么我们需要一个C语言的轻量级RSA库?如果你用C语言做过网络通信、嵌入式设备的数据安全或者软件授权验证,肯定对RSA加密算法不陌生。这个非对称加密的“老将”,从诞生到现在,依然是数字签名、密钥交换场…

2026/7/15 21:40:19阅读更多 →
LangGraph入门:从单节点Hello World图理解状态与执行模型

LangGraph入门:从单节点Hello World图理解状态与执行模型

1. 项目概述:从零开始构建你的第一个 LangGraph 工作流 LangGraph 是当前构建可复现、可调试、可扩展的 LLM 应用工作流最务实的选择之一。它不像某些框架那样追求“一键封装智能”,而是把控制权交还给开发者——用显式的图结构描述数据如何流动、状态如…

2026/7/15 21:40:19阅读更多 →
Flova CLI:基于Agent架构的AI视频生成工具实战指南

Flova CLI:基于Agent架构的AI视频生成工具实战指南

如果你还在为AI视频制作的高门槛而头疼——需要同时掌握剪辑软件、脚本编写、模型调用、参数调整等复杂技能,那么Flova CLI的发布可能正是你期待已久的解决方案。最近正式开放的Flova CLI,本质上是一个基于Agent架构的AI视频生产力工具。它最大的价值在于…

2026/7/16 2:06:01阅读更多 →
【数据结构】【期末冲刺】核心概念与高频考点精讲

【数据结构】【期末冲刺】核心概念与高频考点精讲

1. 数据结构基础概念与算法分析数据结构是计算机存储、组织数据的方式,它研究的是数据的逻辑结构、物理结构以及它们之间的关系。在实际编程中,选择合适的数据结构能大幅提升程序效率。记得我第一次写学生管理系统时,用错了数据结构&#xff…

2026/7/16 2:06:01阅读更多 →
Java随笔-时间复杂度和空间复杂度

Java随笔-时间复杂度和空间复杂度

一、时间复杂度(Time Complexity) 1.1 定义 衡量算法执行时间随输入规模增长的变化趋势。不是实际运行时间(受机器、编译器影响),而是执行语句的次数。 1.2 大 O 表示法规则 只保留最高阶项,忽略常数和低阶…

2026/7/16 2:06:01阅读更多 →
Android随笔-LRU 缓存的底层数据结构详解

Android随笔-LRU 缓存的底层数据结构详解

LRU 缓存的底层数据结构详解 一、LRU 是什么 LRU(Least Recently Used,最近最少使用)是一种缓存淘汰策略:当缓存满时,优先淘汰最久没有被访问的数据。 访问顺序:A → B → C → D → B → A → E缓存容量 …

2026/7/16 2:06:01阅读更多 →
Android—Jetpack教程(二):ViewModel与LiveData实战解析

Android—Jetpack教程(二):ViewModel与LiveData实战解析

1. ViewModel与LiveData为何成为Android开发标配每次屏幕旋转导致Activity重建时,数据丢失的烦恼还记得吗?三年前我维护的一个电商应用就因此频繁出现空指针崩溃。直到Google推出ViewModel和LiveData这对黄金组合,这类问题才迎刃而解。现在它…

2026/7/16 2:06:01阅读更多 →
2026年西湖区大学生学习工具怎么选4个实用选择标准帮你把关

2026年西湖区大学生学习工具怎么选4个实用选择标准帮你把关

"2026年西湖区大学生选学习工具,核心是匹配课堂复习、论文调研、知识自测这些日常校园场景,不用追热门噱头或者贵得离谱的付费工具,记住四个实用选择标准就能帮你把关,避开同学盲安利踩的坑,接下来我会把标准拆解…

2026/7/16 2:01:01阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/15 6:42:19阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/15 6:12:45阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/15 10:54:00阅读更多 →
A--10 Codex Review与GitHub PR工作流实战指南:从代码审查到安全合并

A--10 Codex Review与GitHub PR工作流实战指南:从代码审查到安全合并

摘要:本文系统讲解如何利用Codex App的Review功能与GitHub PR工作流,实现从代码修改到安全合并的完整流程。涵盖Review面板深度使用、/review命令实战、GitHub Connector配置、PR描述撰写技巧,以及常见问题排查方法。通过多个实战案例和流程图,帮助开发者建立高效的AI辅助代…

2026/7/16 0:00:38阅读更多 →
遗传算法解5皇后问题:从Hello World到工业优化的进化实验室

遗传算法解5皇后问题:从Hello World到工业优化的进化实验室

1. 项目概述:为什么用遗传算法解5皇后问题,而不是直接回溯?我带过十几届算法课,也给不少初创团队做过AI架构咨询。每次讲到组合优化问题,学生和工程师的第一反应永远是“写个回溯试试”。这没错——55棋盘上找所有合法…

2026/7/16 0:00:38阅读更多 →
5.1V稳压管输出为何只有4.7V?工作电流与负载影响分析

5.1V稳压管输出为何只有4.7V?工作电流与负载影响分析

前几天调试一个简单的电源模块,用到了5.1V稳压管。电路接好,上电测试,万用表一量——输出居然只有4.7V。第一反应是稳压管坏了,换了一个新的,结果还是4.7V。这让我想起很多初学者都会遇到的困惑:明明标称5.…

2026/7/16 0:00:38阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/15 15:50:47阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/15 8:52:38阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/15 14:06:23阅读更多 →