Google apps script 显示提示后,LockService锁不会持续存在

Google apps script 显示提示后,LockService锁不会持续存在,google-apps-script,google-sheets,locking,Google Apps Script,Google Sheets,Locking,我在GoogleSheets中有一个脚本,当用户单击图像时,它会运行一个函数。该函数修改单元格中的内容,为了避免同时修改,我需要对该函数使用lock 我无法获取,为什么这不起作用我仍然可以从不同的客户端多次调用相同的函数: function placeBidMP1() { var lock = LockService.getScriptLock(); lock.waitLock(10000) placeBid('MP1', 'I21:J25'); lock.releaseLock

我在GoogleSheets中有一个脚本,当用户单击图像时,它会运行一个函数。该函数修改单元格中的内容,为了避免同时修改,我需要对该函数使用lock

我无法获取,为什么这不起作用我仍然可以从不同的客户端多次调用相同的函数:

function placeBidMP1() {
  var lock = LockService.getScriptLock();
  lock.waitLock(10000)
  placeBid('MP1', 'I21:J25');
  lock.releaseLock();
}
placeBid功能如下:

    function placeBid(lotName, range) {
      var firstPrompt = ui.prompt(lotName + '-lot', 'Please enter your name:', ui.ButtonSet.OK); 
      var firstPromptSelection = firstPrompt.getSelectedButton(); 
      var userName = firstPrompt.getResponseText();
  
    if (firstPromptSelection == ui.Button.OK) {
    
      do {
        
        var secondPrompt = ui.prompt('Increase by', 'Amount (greater than 0): ', ui.ButtonSet.OK_CANCEL);  
        var secondPromptSelection = secondPrompt.getSelectedButton(); 
        var increaseAmount = parseInt(secondPrompt.getResponseText());
        
      } while (!(secondPromptSelection == ui.Button.CANCEL) && !(/^[0-9]+$/.test(increaseAmount)) && !(secondPromptSelection == ui.Button.CLOSE));
    
    if (secondPromptSelection != ui.Button.CANCEL & secondPromptSelection != ui.Button.CLOSE) {
      
        var finalPrompt = ui.alert("Price for lot will be increased by " + increaseAmount + " CZK. Are you sure?", ui.ButtonSet.YES_NO);
        if (finalPrompt == ui.Button.YES) {
          
          var cell = SpreadsheetApp.getActiveSheet().getRange(range);
          var currentCellValue = Number(cell.getValue());
          cell.setValue(currentCellValue + Number(increaseAmount));
          bidsHistorySheet.appendRow([userName, lotName, cell.getValue()]);
          SpreadsheetApp.flush();
          showPriceIsIncreased();
          
        } else {showCancelled();}
    } else {showCancelled();}
  } else {showCancelled();}
}
我有几个placeBidMP函数用于工作表上的不同元素,只需锁定单独的函数,以避免多次调用

下一步我也尝试过:

if (lock.waitLock(10000)) {
 placeBidMP1(...);
} 
else {
 showCancelled();
}
在本例中,它会立即显示取消弹出窗口。

我仍然可以从不同的客户端多次调用相同的函数

这一点很清楚:prompt方法不会持久化LockService锁,因为它会暂停等待用户交互的脚本执行:

在用户取消对话框后,脚本将继续,但Jdbc连接和LockService锁不会在整个暂停期间保持

在这种情况下,它会立即显示取消弹出窗口

这里也没什么奇怪的-if语句计算条件中的内容并将结果强制为布尔值。看看waitLock方法签名——它返回void,这是一个虚假的值。您基本上创建了这个:iffalse,这就是Show立即取消开火的原因

变通办法

您可以通过模拟Lock类所做的工作来绕过该限制。请注意,这种方法并不意味着要取代该服务,而且也存在一些限制,特别是:

PropertiesService在读/写操作上具有。这是一个慷慨的选择,但您可能希望将toSleep interval设置为更高的值,以避免以牺牲精度为代价消耗配额。 不要用这个自定义实现替换Lock类-V8不会将代码放在特殊上下文中,因此服务是直接公开的,可以被重写。 版本2

下面的内容更接近原始版本,解决了将PropertiesService用作解决方案的一个重要问题:如果在执行获取锁的函数期间存在未处理的异常,则上述版本将无限期地卡住锁,可以通过删除相应的脚本属性来解决

以下版本或使用自删除基于时间的触发器,该触发器设置为在脚本的当前最大执行时间超过30分钟后触发,如果希望提前清理,可以将其配置为较低的值:

var PropertyLock = (() => {

    let locked = false;
    let timeout = 0;

    const store = PropertiesService.getScriptProperties();

    const propertyName = "locked";
    const triggerName = "PropertyLock.releaseLock";

    const toSleep = 10;
    const currentGSuiteRuntimeLimit = 30 * 60 * 1e3;

    const lock = function () { };

    /**
     * @returns {boolean}
     */
    lock.hasLock = function () {
        return locked;
    };

    /**
     * @param {number} timeoutInMillis 
     * @returns {boolean}
     */
    lock.tryLock = function (timeoutInMillis) {

        //emulates "no effect if the lock has already been acquired"
        if (locked) {
            return true;
        }

        timeout === 0 && (timeout = timeoutInMillis);

        const stored = store.getProperty(propertyName);
        const isLocked = stored ? JSON.parse(stored) : false;

        const canWait = timeout > 0;

        if (isLocked && canWait) {
            Utilities.sleep(toSleep);

            timeout -= toSleep;

            return timeout > 0 ?
                PropertyLock.tryLock(timeoutInMillis) :
                false;
        }

        if (!canWait) {
            return false;
        }

        try {
            store.setProperty(propertyName, true);

            ScriptApp.newTrigger(triggerName).timeBased()
                .after(currentGSuiteRuntimeLimit).create();

            console.log("created trigger");
            locked = true;

            return locked;
        }
        catch (error) {
            console.error(error);
            return false;
        }
    };

    /**
     * @returns {void}
     */
    lock.releaseLock = function () {

        try {
            locked = false;
            store.setProperty(propertyName, locked);

            const trigger = ScriptApp
                .getProjectTriggers()
                .find(n => n.getHandlerFunction() === triggerName);

                console.log({ trigger });

            trigger && ScriptApp.deleteTrigger(trigger);
        }
        catch (error) {
            console.error(error);
        }

    };

    /**
     * @param {number} timeoutInMillis
     * @returns {boolean}
     * 
     * @throws {Error}
     */
    lock.waitLock = function (timeoutInMillis) {
        const hasLock = PropertyLock.tryLock(timeoutInMillis);

        if (!hasLock) {
            throw new Error("Could not obtain lock");
        }

        return hasLock;
    };

    return lock;
})();

var PropertyLockService = (() => {
    const init = function () { };

    /**
     * @returns {PropertyLock}
     */
    init.getScriptLock = function () {
        return PropertyLock;
    };

    return init;
})();
请注意,第二个版本使用静态方法,与LockService一样,不应实例化。您可以使用类和静态方法来强制执行此操作

参考资料

等待锁方法 提示法 JavaScript中的错误 我仍然可以从不同的客户端多次调用相同的函数

这一点很清楚:prompt方法不会持久化LockService锁,因为它会暂停等待用户交互的脚本执行:

在用户取消对话框后,脚本将继续,但Jdbc连接和LockService锁不会在整个暂停期间保持

在这种情况下,它会立即显示取消弹出窗口

这里也没什么奇怪的-if语句计算条件中的内容并将结果强制为布尔值。看看waitLock方法签名——它返回void,这是一个虚假的值。您基本上创建了这个:iffalse,这就是Show立即取消开火的原因

变通办法

您可以通过模拟Lock类所做的工作来绕过该限制。请注意,这种方法并不意味着要取代该服务,而且也存在一些限制,特别是:

PropertiesService在读/写操作上具有。这是一个慷慨的选择,但您可能希望将toSleep interval设置为更高的值,以避免以牺牲精度为代价消耗配额。 不要用这个自定义实现替换Lock类-V8不会将代码放在特殊上下文中,因此服务是直接公开的,可以被重写。 版本2

下面的内容更接近原始版本,解决了将PropertiesService用作解决方案的一个重要问题:如果在执行获取锁的函数期间存在未处理的异常,则上述版本将无限期地卡住锁,可以通过删除相应的脚本属性来解决

以下版本或使用自删除基于时间的触发器,该触发器设置为在脚本的当前最大执行时间超过30分钟后触发,如果希望提前清理,可以将其配置为较低的值:

var PropertyLock = (() => {

    let locked = false;
    let timeout = 0;

    const store = PropertiesService.getScriptProperties();

    const propertyName = "locked";
    const triggerName = "PropertyLock.releaseLock";

    const toSleep = 10;
    const currentGSuiteRuntimeLimit = 30 * 60 * 1e3;

    const lock = function () { };

    /**
     * @returns {boolean}
     */
    lock.hasLock = function () {
        return locked;
    };

    /**
     * @param {number} timeoutInMillis 
     * @returns {boolean}
     */
    lock.tryLock = function (timeoutInMillis) {

        //emulates "no effect if the lock has already been acquired"
        if (locked) {
            return true;
        }

        timeout === 0 && (timeout = timeoutInMillis);

        const stored = store.getProperty(propertyName);
        const isLocked = stored ? JSON.parse(stored) : false;

        const canWait = timeout > 0;

        if (isLocked && canWait) {
            Utilities.sleep(toSleep);

            timeout -= toSleep;

            return timeout > 0 ?
                PropertyLock.tryLock(timeoutInMillis) :
                false;
        }

        if (!canWait) {
            return false;
        }

        try {
            store.setProperty(propertyName, true);

            ScriptApp.newTrigger(triggerName).timeBased()
                .after(currentGSuiteRuntimeLimit).create();

            console.log("created trigger");
            locked = true;

            return locked;
        }
        catch (error) {
            console.error(error);
            return false;
        }
    };

    /**
     * @returns {void}
     */
    lock.releaseLock = function () {

        try {
            locked = false;
            store.setProperty(propertyName, locked);

            const trigger = ScriptApp
                .getProjectTriggers()
                .find(n => n.getHandlerFunction() === triggerName);

                console.log({ trigger });

            trigger && ScriptApp.deleteTrigger(trigger);
        }
        catch (error) {
            console.error(error);
        }

    };

    /**
     * @param {number} timeoutInMillis
     * @returns {boolean}
     * 
     * @throws {Error}
     */
    lock.waitLock = function (timeoutInMillis) {
        const hasLock = PropertyLock.tryLock(timeoutInMillis);

        if (!hasLock) {
            throw new Error("Could not obtain lock");
        }

        return hasLock;
    };

    return lock;
})();

var PropertyLockService = (() => {
    const init = function () { };

    /**
     * @returns {PropertyLock}
     */
    init.getScriptLock = function () {
        return PropertyLock;
    };

    return init;
})();
请注意,第二个版本使用静态方法,与LockService一样,不应实例化。您可以使用类和静态方法来强制执行此操作

参考资料

等待锁方法 提示法 JavaScript中的错误
谢谢你的回答!我在看这个文件
n在这里发布之前,没有发现任何与提示暂停效果相关的内容:在这种情况下,我的问题还有其他可能的解决方案吗?我只需要确保在运行脚本时,两个人不能同时修改一个特定的单元格。不用担心,我们都会时不时地看到模糊的眼睛:PropertiesService有一个解决方法,我会在这里发布它,当我确定它正确工作时。您还可以将逻辑移到提示之外,并且仅在实际编辑发生时锁定-这将导致更糟糕的用户体验,但可能是唯一的其他选项!在这里发布之前,我正在阅读此文档,没有发现任何与提示暂停效果相关的内容:在这种情况下,我的问题是否还有其他潜在的解决方案?我只需要确保在运行脚本时,两个人不能同时修改一个特定的单元格。不用担心,我们都会时不时地看到模糊的眼睛:PropertiesService有一个解决方法,我会在这里发布它,当我确定它正确工作时。您还可以做的是将逻辑移到提示之外,并且仅在实际编辑发生时锁定-这将导致更糟糕的用户体验,但可能是唯一的其他选项