Google apps script 显示提示后,LockService锁不会持续存在
我在GoogleSheets中有一个脚本,当用户单击图像时,它会运行一个函数。该函数修改单元格中的内容,为了避免同时修改,我需要对该函数使用lock 我无法获取,为什么这不起作用我仍然可以从不同的客户端多次调用相同的函数: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
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有一个解决方法,我会在这里发布它,当我确定它正确工作时。您还可以做的是将逻辑移到提示之外,并且仅在实际编辑发生时锁定-这将导致更糟糕的用户体验,但可能是唯一的其他选项