GM_lock

创建于 1 天前
更新于 1 天前
一个用于用户脚本的轻量级跨标签页异步锁
#GM
总安装量
5
今日新增
+1
用户评分
- / 5.0 (0)
当前版本
0.1.0
// @require https://scriptcat.org/lib/4709/0.1.0/GM_lock.js?sha384-dbz4ENOCrR50Xa7gxF6jsZLb8ExaYEMuOpTpDgw6Xt4yavf5ZgoPnbaoN4M3MIMQ
库详情
这是一个用户脚本使用的库,你可以在你的脚本中直接引用它。

GM_lock — 一个用于用户脚本的轻量级跨标签页异步锁

一个轻量、无依赖的用户脚本互斥锁(mutex),通过 GM.setValue + GM_addValueChangeListener 进行协调,因此在任意时刻 只有一个标签页/上下文 会执行临界区代码。适用于共享同一用户脚本存储的多个标签页和 iframe。

安装 / @require
这是一个库,而不是独立脚本。请在你的用户脚本头部加入:

// @require https://scriptcat.org/lib/4709/0.1.0/GM_lock.js?sha384-dbz4ENOCrR50Xa7gxF6jsZLb8ExaYEMuOpTpDgw6Xt4yavf5ZgoPnbaoN4M3MIMQ

API

// JavaScript
var GM_lock: (tag: string, func: () => (void | Promise<void>)) => Promise<void>;
  • tag:用于标识锁作用域的字符串。相同 tag ⇒ 相同锁。

  • func:你的临界区(可同步或异步)。

  • 返回:当 func 执行完成后 resolve 的 Promise<void>

    • func 中的错误会被捕获并打印到控制台;它们不会导致 promise reject。

注意: 当前实现不会返回 func 的返回值(始终以 void 解析)。如果需要返回结果,请在 func 内写入外部变量或存储。


使用示例

// 所有标签页中同一时刻只有一个实例会进入此代码块
await GM_lock('sync-cache', async () => {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();
  // 处理数据;按需持久化
  await GM.setValue('cache:data', data);
});

按功能使用简短、稳定的 tag,例如:'queue:upload''cache:refresh'


管理器兼容性与所需授权

需要支持以下 API 的用户脚本管理器:

  • GM.setValue
  • GM_addValueChangeListener
  • GM_removeValueChangeListener

可选/条件需求(仅在启用 cleanup 时需要;见 Options):

  • GM.deleteValues(或旧版别名 GM_deleteValues

已在 ScriptCat、Tampermonkey、Violentmonkey 测试;其他支持上述 API 的环境理论上也可使用。


工作原理(高层概述)

  • 每个竞争者会构造一个 锁 idid = <大时间戳>_<随机 base36>,并使用带有以下前缀的 按 tag 分组 键:

    • ack 键:GM_lock::<tag>::ack
    • nxt 键:GM_lock::<tag>::nxt
  • 竞争者通过写入 ack 来进行“宣布”,然后通过 nxt 中转。

  • 同时竞争的伙伴会收集在同一 ack 时间点 写入的竞争者,经过一个小的 冲突间隔(默认约 500 ms)后,通过对收集的 id 排序并选择最小者,确定胜者。胜者进入 func

  • 执行完毕后,胜者再次更新 ack 以释放锁,若需要则允许下一轮选举。

这种事件驱动的设计能最小化轮询并在多个标签页/框架间进行良好协调。


配置项(源代码中的内部常量)

  • COLLISION_INTERVAL = 500 —— 在选举胜者前的等待,用于减少竞争窗口。
  • CLEANUP_VALUES = false —— 若设为 true,使用后会移除辅助键(需要 GM.deleteValues)。
  • SHOW_LOG = false —— 若设为 true,通过 GM.setValue 输出时间标记(调试用)。

说明与注意事项

  • 不要在 func 中长时间阻塞事件循环;优先使用 await 异步工作。(其他竞争者依赖事件和短计时器进行协调。)
  • 如果某标签页在持锁时崩溃,下一轮选举会因 ack/nxt 刷新和间隔机制而继续推进。
  • 当前实现 仅记录错误而不 reject;如需错误上报,请在 func 内自行处理。

许可协议

公共领域 — The Unlicense

本库在源码头部附带完整 Unlicense 声明,简要概括:

  • 你可以自由复制、修改、发布、使用、编译、销售、再分发本软件,
    用于任何目的(商业或非商业),以任何方式。
  • 作者放弃在版权法下的全部权利,不对软件做任何形式的担保。
  • 使用造成的任何损失,作者概不负责。

详情可参考:https://unlicense.org