Greasy Fork

Greasy Fork is available in English.

GM_lock

A lightweight, dependency-free mutex for Userscripts that ensures **only one tab / context** runs a critical section at a time. It coordinates through `GM.setValue` + `GM_addValueChangeListener`, so it works across multiple tabs, iframes, and even separate scripts that share the same @name/@namespace storage.

当前为 2025-11-01 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/554436/1687613/GM_lock.js

/**
 * 
 * MIT License
 * 
 */
var GM_lock = async (tag, func) => {
    const lockId = `${Date.now() + 1000000000000000}_${Math.floor(Math.random() * 9000 + 1000)}`;
    let resolveFn, rejectFn, cid;
    const promiseRet = new Promise((resolve, reject) => {
        resolveFn = resolve;
        rejectFn = reject;
    });
    let valids = new Set();
    let listenerId = GM_addValueChangeListener(`GM_lock_changed::${tag}`, async (_key, _oldValue, newValue, _remote) => {
        valids.add(newValue.split("_")[0]);
        const keys = (await GM.listValues()).filter((key) => key.startsWith(`GM_lock::${tag}::`)).sort();
        if (keys[0] === `GM_lock::${tag}::${lockId}`) {
            GM_removeValueChangeListener(listenerId);
            clearTimeout(cid);
            await GM.setValue(`GM_lock_changed::${tag}`, `${lockId}_run_${Date.now()}`);
            let res;
            let err;
            try {
                res = await func();
            } catch (e) {
                err = e;
            }
            await GM.deleteValue(`GM_lock::${tag}::${lockId}`);
            await GM.setValue(`GM_lock_changed::${tag}`, `${lockId}_del_${Date.now()}`);
            err ? rejectFn(err) : resolveFn(res);
        }
    });
    await GM.setValue(`GM_lock::${tag}::${lockId}`, "1");
    await new Promise(resolve => setTimeout(resolve, 50));  // 可选:设置 delay 确保锁不会冲突.
    await GM.setValue(`GM_lock_changed::${tag}`, `${lockId}_set_${Date.now()}`);
    // 因旧记录,导致1秒内没触发,重设锁
    cid = setTimeout(async () => {
        const keys = (await GM.listValues()).filter((key) => key.startsWith(`GM_lock::${tag}::`)).sort();
        for (const key of keys) {
            if (key === `GM_lock::${tag}::${lockId}`) break;
            let isValid = false;
            for (const part of valids) {
                if (key.includes(`${part}_`)) isValid = true;
            }
            if (isValid) break;
            await GM.deleteValue(key);
        }
        valids.clear();
        await GM.setValue(`GM_lock::${tag}::${lockId}`, "1");
        await new Promise(resolve => setTimeout(resolve, 50));  // 可选:设置 delay 确保锁不会冲突.
        await GM.setValue(`GM_lock_changed::${tag}`, `${lockId}_set_${Date.now()}`);
    }, 1000);
    return promiseRet;
};