按了取消,进度区停在 44%
同事要一个 1 GB 的本地读写用例,直接在浏览器里造。填 1 GB、点生成,进度到 44% 时我怕页面撑不住,按了取消。按钮当场恢复可点,进度区却仍挂在原处:44%、454.00 MB、耗时 0.7s 一个字没清。为什么停在 454 这种不上不下的数上?答案在块边界。
文件是一块块攒出来、最后一次性打包的
生成只有一个 while 循环:源码写死 var chunkSize = 1024 * 1024,每轮产出一个 1048576 字节的 Uint8Array 推进本地数组,末轮按 Math.min(chunkSize, remaining) 截短;数组攒齐才 new Blob,标签到 正在打包下载... 才改写。块数就是目标字节除以 1048576:
| 目标 | 块数 | 末块字节 |
|---|---|---|
| 3 B | 1 | 3 |
| 1.5 MB | 2 | 524288 |
| 10 MB | 10 | 1048576 |
| 500 MB | 500 | 1048576 |
| 1 GB | 1024 | 整除,无短块 |
1073741824 / 1048576 = 1024,1 GB 恰好由 1024 个等长块拼成,没有截短的那一轮。浏览器整跑一次 1449ms,面板定稿 1.45s,Blob 收到的 parts 数正是 1024;同机 Node 版逐块落盘 1494ms,fs.statSync 报 1073741824 字节。模式也影响快慢:在任意大小文件生成面板里 100 MB 随机数据 186ms、全零 133ms,那 53ms 正是随机分支逐 65536 字节灌满的开销。
取消只有一个赋值,停止落在块边界
cancelGenerate 函数体只有 faCancelled = true 一行。检查点在循环里每块开始之前,命中就弹「已取消生成」后 break——取消不是踩刹车,而是"下一块不再生成",最长延迟是一块生成时间加一次 15ms 让出。1 GB 样本里 721ms 按下取消,停在第 454 块、44%,折合约 1.59ms/块;同次实验里 Blob 构造、createObjectURL、a.click 计数全是 0,取消路径根本没进打包段,"已生成的 blob 去了哪"没有对象——那 454 块挂在局部数组上,返回后出作用域。
图 1:1 GB 取消样本的块边界与界面残留
复位只覆盖两个按钮
resetUI 只做两件事:生成按钮解禁、取消按钮藏起来。进度区不在清单上——44%、454.00 MB、耗时 0.7s 原地留着,结果面板仍 display:none。faCancelled 也不自清,靠下一轮 generate 首行重置。补跑一次 10 MB 验证污染:toast 只有「文件已生成并开始下载」,面板正常出「文件名:testfile.bin大小:10.00 MB (10,485,760 字节)耗时:0.02s模式:随机数据」。残留的是显示,不是状态。
每块写 3 个文本,进度条只动 45 次
454 块期间,百分比、已生成、耗时三个元素各写 454 次文本,进度条的 style 属性却只变 45 次——百分比取整后连续约 10 块同值,重复赋值不算变更。10 MB 那轮变更 11 次,是 10 块循环加收尾一次 100%。耗时也有两套精度:进度区取 1 位小数(0.7s),定稿取 2 位(0.02s、1.45s)。
三种填充模式往字节里铺了什么
全零分支直接 return 清零的块,首尾 16 字节全是 00。随机分支按 Math.min(65536, size - off) 分 16 次灌满一整块,同块内 0x00 出现 4078 次,与每字节 256 种取值算出的 4096 次均匀期望对得上。自定义是 TextEncoder 编码后循环覆写:「你好abc」UTF-8 是 9 字节 e4 bd a0 e5 a5 bd 61 62 63,铺进 20 字节块得两个整循环再接 e4 bd;「AB」灌 5 字节块得 41 42 41 42 41,即 ABABA。空文本会退化成全零,却被第 4 道守卫「请输入自定义填充文本」拦在界面外。
图 2:三种填充模式的字节实测与循环截尾
显示值与真实字节差在两处
一处在首屏:fa-size-val 的静态初值写成 1.00 MB (1048576 字节),初始化那次 updateSizeInfo() 一跑就被带千分位的 1.00 MB (1,048,576 字节) 盖掉。另一处在变色:红字按真实字节数比较(bytes > 500 * 1048576 才写 #ff6666),显示值却先四舍五入——524288001 字节写着「500.00 MB」,颜色早已是警示红。进度区只给 humanReadable,不带精确字节。
四道守卫与一条硬顶
generate 顶部排着 4 道关卡。0、负数、空串、非数字在 getTotalBytes 里同走 return 0,界面只有一条「请输入有效的文件大小」。超 1GB 那条排在确认框之前:填 1024 GB(1073741824000 字节)只弹「文件过大,请勿超过 1GB」,确认框一次没弹。恰好 1 GB 因严格大于而合法,但要过 500MB 确认;524288000 字节同样严格大于,静默生成。600 MB 那次点了否:toast 空、Blob 构造 0 次、按钮连 disabled 都没进过。
下载链接只活 1000 毫秒
文件名取 value.trim() || "testfile.bin",只去首尾空格:输入 " mydata .dat " 得 download 属性 mydata .dat,noext 不补扩展名。成功链路里 createObjectURL 到 revokeObjectURL 实测间隔 1000ms,file:// 源下前缀形如 blob:null/,1 秒后即被回收。
FAQ
Q:取消后进度区那几个数字还能信吗?
是取消那一刻的真实读数:454.00 MB 与 44% 对应已产出的 454 块,只是不再更新,resetUI 没管进度区。
Q:为什么 1 GB 反而没有末块?
1024 整除没有余数,Math.min(chunkSize, remaining) 在这一档永远取到整块;换成 1.5 MB 才截出 524288 的短块。
Q:在 500MB 确认框点了否,算一次失败吗?
不算,它连界面状态都没改。这次该填多大,同工具另有一篇旧文 按需造一个指定大小的测试文件。