这篇文章发布于 2026年09月10日,星期四,18:37,归类于 JS API。 阅读 55 次, 今日 53 次 没有评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12396
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
一、大文件断点续传
大文件上传,为了用户体验良好,通常需要接入断点续传。
相关前端实现我十几年前就有介绍过“HTTP协议下文件上传断点续传”。
不过本文要讲的重点不是断点续传本身,而是其中可能遇到的一个用户体验问题。
那就是如果用户同时打开多个同一个URL地址的文件续传标签页,此时该怎么办?

如果我们放任不管,那么这几个页面都会自主主张给后端发送上传数据。
那还得了,每个页面的上传进度不一样的,后端返回岂不是一会儿30%,一会儿25%,一会儿又是35%。
于是就会看到进度条像得了羊癫疯一样,疯狂跳动,来,社会摇,走起来。
这种疯癫的交互效果就算是精神小妹都无法容忍。
显然需要处理?
该怎么处理呢。
二、跨浏览器标签页开发常见方案
我们不妨看看市面上现有的,跨页面的通信交互方案,能不能优雅地解决此问题。
1. 本地localStorage + 轮询
// 示意代码,勿使用
if (!localStorage.getItem('upload-lock')) {
localStorage.setItem('upload-lock', Date.now());
// 开始上传
// ...
}
问题在于:
- 竞态条件:localStorage 的读/写操作不是原子操作。所谓原子操作,指的是一次性完整执行完毕的操作。例如:
// 逻辑:读取计数 +1 再存回去 let count = Number(localStorage.getItem('counter')); // ①读 count = count + 1; // ②内存修改 localStorage.setItem('counter', count); // ③写上面代码的读本身,和写本身可以看成是原子操作,但是,整个任务却不是原子的,因为读和写中间有一段业务计算,整套操作被拆分,中间可以被别的标签页抢占。
- 过期锁定:如果标签页崩溃,锁定状态将永久保留。
- 轮询开销:您需要持续检查锁定状态
- 事件限制:存储事件只会在其他标签页中触发,而不会在当前标签页中触发。
2. Broadcast Channel API
广播通道API我去年也介绍过,详见“Broadcast Channel API简介,可实现Web页面广播通信”一文。

如果是针对当前需求,我们可能会这么处理:
const channel = new BroadcastChannel('upload-coordination');
channel.postMessage({ type: 'REQUEST_UPLOAD_PERMISSION' });
看起来代码还挺简单,不过问题也比较致命。
那就是同时发送的消息可能会造成冲突,需要手动处理心跳检测、崩溃检测和冲突解决。
可以看到,那些常见的跨页面通信手段,都无法真正解决这里的共享文件上传问题。
其实,类似这种多页面操作统一资源的场景,毫无疑问,最好的解决方案就是Web Locks API。
三、了解“锁”的概念
“锁”是计算机开发中的一个重要概念,前端开发人员接触的不多,但是如果是数据库开发,那这个可太熟了。
锁其实是一种机制,它确保共享资源(变量、对象、函数调用等)一次只能被一个对象访问,并且保证每个调用者都能获得最新信息。
比方说:一家银行在共享内存中存储了多个账户,并且有多台ATM机同时进行取款、存款和查询余额的操作。
如果没有协调(同步),这将导致混乱,如下图所示。

此时,我们可以通过锁定账户的读写权限来控制访问。
这样一来,无论执行什么操作(取款、充值或查询余额),第一个获得锁的调用者都将获得该锁。
其他调用者必须等到第一个获得锁的调用者完成操作后,锁才会被释放。
下图展示了同步账户访问后的情况。

四、navigator.locks与上传锁定
OK,万事具备,就看Web Locks API如何在代码层面解决我们的上传冲突问题了,其实代码很简单。
// 只有成功获取锁才继续处理
navigator.locks.request(LOCK_NAME, { ifavailable: true }
async (lock) => {
if (lock) {
// 执行上传
upload();
await holdLockUntilComplete(abortController);
}
// 获取不到锁则静默跳过 - 其他标签页正在处理
// ...
}
);
看到没有,就几行代码。
使用非常简单,就是把原来的实现代码,使用navigator.locks.request()方法包一下就好了。
此时,浏览器会自动加锁和锁判断。
锁的生命周期管理示意
上述代码中的holdLockUntilComplete()可以用来中断上传请求,或者用来管理整个上传的生命周期。
使用示意,供大家参考。
const holdLockUntilComplete = (abortController) => {
return new Promise((resolve) => {
const cleanup = () => {
// 移除所有的监听事件
uploader.off("complete", cleanup);
resolve();
};
// 上传完成事件
uploader.on("complete", cleanup);
// 其他事件略...
// 组件卸载时也释放
abortController.signal.addEventListener("abort", cleanup);
});
};
使用 LockManager 的好处
- 真正解决冲突问题,零重复上传
- 更好的用户体验,由于协调逻辑,上传过程不会再出现卡顿。
- 降低基础设施成本,不再需要重复处理和存储
- 更简洁的代码,没有复杂的状态机或轮询逻辑
五、一些可选参数说明
navigator.locks.request()方法是支持一些可选参数的,语法如下:
request(name, callback) request(name, options, callback)
其中,options支持以下一些参数,这些参数大多数场景下都用不到,大家一眼扫一下就可以了。
- mode
-
"exclusive"或"shared"之一。默认值是"exclusive",表示排他,也就是一次只能一个锁。"shared"表示共享锁。 - ifAvailable
-
如果为
true,则只有在尚未持有锁的情况下才会授予锁请求。如果无法授予,则将使用null而不是Lock实例来调用回调。默认值为false。 - steal
-
如果为
true,将释放所有同名已持有的锁,并授予该请求。默认值为false。警告:小心使用!之前在锁内运行的代码会继续运行,并且可能与现在持有锁的代码发生冲突。
- signal
-
一个
AbortSignal(AbortController的signal属性);如果指定并且AbortController被中止,则锁请求将被丢弃(如果尚未授予)。
其他适合使用Locks API的场景
除了本文提到的大文件断点续传,还有以下这些需求场景适合使用 Web Locks API.
- 当多个标签页同时操作 Indexed Database API 或 LocalStorage 时;
- 在网页端文档编辑器中,若用户在两个标签页中打开了同一份文档;
- 在单页面内按顺序执行复杂的异步任务(如批量文件上传、连续串联请求)时;
还有下图所示的情况,也有必要上锁

六、兼容性以及其他说明
Web Locks API已经出现很多年了,兼容性还是相当OK的,以目前的AI能力,此API使用是没有任何困扰的。

除了上面介绍过的request()方法,navigator.locks还有个query()方法,此方法执行后返回一个个Promise,该Promise解析后得到一个对象,其中包含有关已持有锁和挂起锁的信息。
具体有哪些信息,不展开介绍,因为没有意义,正遇到类似需求,直接让AI去运行代码就好了。
好了,本文内容已经比预期的多多了。
其实没必要扯那么多东西的,如今AI这么强,技术细节并没有那么重要。

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12396
(本篇完)
- 突破本地离线存储5M限制的JS库localforage简介 (0.345)
- 务必谨慎使用JS WeakRef弱引用 (0.301)
- Broadcast Channel API简介,可实现Web页面广播通信 (0.283)
- 使用AbortController abort中断原生fetch或axios请求 (0.188)
- 别再使用IndexedDB,大文件读写就用OPFS (0.133)
- 漫谈js自定义事件、DOM/伪DOM自定义事件 (0.113)
- 小tips: 点击页面出现富强、民主这类文字动画效果 (0.113)
- JS CustomEvent自定义事件传参小技巧 (0.113)
- 隐私相关-了解HTML5 Do Not Track API (0.071)
- 使用navigator.connection.downlink前端测网速 (0.071)
- HTML permission元素和Permissions API简介 (RANDOM - 0.071)