我编写了一个自定义 Google Apps 脚本,它将接收id
并从网络服务获取信息(价格)。
我在电子表格中使用这个脚本,它工作得很好。我的问题是这些价格发生了变化,而我的电子表格没有更新。
如何强制它重新运行脚本并更新单元格(无需手动检查每个单元格)?
我编写了一个自定义 Google Apps 脚本,它将接收id
并从网络服务获取信息(价格)。
我在电子表格中使用这个脚本,它工作得很好。我的问题是这些价格发生了变化,而我的电子表格没有更新。
如何强制它重新运行脚本并更新单元格(无需手动检查每个单元格)?
好的,我的问题似乎是谷歌的行为方式很奇怪——只要脚本参数相似,它就不会重新运行脚本,它使用之前运行的缓存结果。因此,它不会重新连接到 API,也不会重新获取价格,它只是返回之前缓存的脚本结果。
在此处查看更多信息(如果您受到影响,请为这些问题添加星标):
我的解决方案是在我的脚本中添加另一个参数,我什至不使用它。现在,当您使用与以前调用不同的参数调用函数时,它将不得不重新运行脚本,因为这些参数的结果不会在缓存中。
因此,每当我调用该函数时,对于额外的参数,我都会传递“$A$1”。我还创建了一个名为 refresh 的菜单项,当我运行它时,它将当前日期和时间放在 A1 中,因此所有以 $A$1 作为第二个参数的脚本调用都必须重新计算。这是我脚本中的一些代码:
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Refresh",
functionName : "refreshLastUpdate"
}];
sheet.addMenu("Refresh", entries);
};
function refreshLastUpdate() {
SpreadsheetApp.getActiveSpreadsheet().getRange('A1').setValue(new Date().toTimeString());
}
function getPrice(itemId, datetime) {
var headers =
{
"method" : "get",
"contentType" : "application/json",
headers : {'Cache-Control' : 'max-age=0'}
};
var jsonResponse = UrlFetchApp.fetch("http://someURL?item_id=" + itemId, headers);
var jsonObj = eval( '(' + jsonResponse + ')' );
return jsonObj.Price;
SpreadsheetApp.flush();
}
当我想将 ID 为 5 的商品的价格放入单元格时,我使用以下公式:
=getPrice(5, $A$1)
当我想刷新价格时,我只需单击“刷新”->“刷新”菜单项。请记住,您需要在更改onOpen()
脚本后重新加载电子表格。
您错过了挑剔的缓存错误功能。它是这样工作的:
Google 认为您的所有自定义函数仅直接依赖于它们的参数值来返回它们的结果(您可以选择依赖其他静态数据)。
鉴于此先决条件,他们只能在参数更改时评估您的函数。例如
假设我们在单元格 B1 上有文本“10”,然后在我们键入的其他单元格上=myFunction(B1)
myFunction 将被评估并检索其结果。然后,如果您将单元格 B1 的值更改为“35”,自定义将按预期重新评估,并正常检索新结果。现在,如果您再次将单元格 B1 更改为原始的“10”,则不会重新评估,会立即从缓存中检索原始结果。
因此,当您使用工作表名称作为参数来动态获取它并返回结果时,您就违反了缓存规则。
不幸的是,如果没有这个惊人的功能,您就无法拥有自定义功能。因此,您必须更改它以直接接收值,而不是工作表名称,或者不使用自定义函数。例如,您可以在脚本中设置一个参数来告诉摘要应该去哪里,并onEdit
在总数发生变化时更新它们。
我所做的与 tbkn23 类似。除了进行更改之外,此方法不需要任何用户操作。
我要重新评估的函数有一个额外的未使用参数 $A$1。所以函数调用是
=myFunction(firstParam, $A$1)
但在代码中,函数签名是
function myFunction(firstParam)
我没有使用 Refresh 函数,而是使用了这样的 onEdit(e) 函数
function onEdit(e)
{
SpreadsheetApp.getActiveSheet().getRange('A1').setValue(Math.random());
}
每当编辑电子表格中的任何单元格时都会触发此功能。所以现在你编辑一个单元格,在 A1 中放置一个随机数,这会按照 tbkn23 的建议刷新参数列表,导致重新评估自定义函数。
如果您的自定义函数位于特定列中,只需按该列对电子表格进行排序。
排序操作会强制刷新数据,这会立即为该列的所有行调用您的自定义函数。
除非参数更改,否则自定义函数不会更新。
创建一个onChange
触发器来更改电子表格中所有自定义函数的所有参数,使用TextFinder
通过@tbkn23添加一个额外的虚拟参数并通过@Lexi Brush使用触发器的想法在这里实现了一个随机数作为参数。这个答案主要是由于类的使用TextFinder
(Apps 脚本的一个相对较新的添加)而有所不同,这更好,因为
不需要额外的单元格。
不需要菜单 > 不需要额外的点击。如果您需要自定义复习,复选框是更好的实现
您还可以更改公式本身而不是更改参数
更改/触发器可以配置为仅过滤掉某些更改。例如,以下示例脚本触发器过滤掉除INSERT_GRID/REMOVE_GRID
(Grid=Sheet) 之外的所有更改。这适用于提供sheetnames
. 任何地方的编辑都不会更改工作表/工作表名称的列表,但插入或删除工作表会。
/**
* @customfunction
* @returns Current list of sheet names
*/
function sheetNames(e) {
return SpreadsheetApp.getActive()
.getSheets()
.map(function(sheet) {
return sheet.getName();
});
}
/**
* @listens to changes in a Google sheet
* @see https://developers.google.com/apps-script/guides/triggers/installable#managing_triggers_manually
*/
function onChange(e) {
/* Following types of change are available:
* EDIT
* INSERT_ROW
* INSERT_COLUMN
* REMOVE_ROW
* REMOVE_COLUMN
* INSERT_GRID
* REMOVE_GRID
* FORMAT
* OTHER - This usually refers to changes made by the script itself or sheets api
*/
if (!/GRID/.test(e.changeType)) return; //Listen only to grid change
SpreadsheetApp.getActive()
.createTextFinder('=SHEETNAMES\\([^)]*\\)')
.matchFormulaText(true)
.matchCase(false)
.useRegularExpression(true)
.replaceAllWith(
'=SHEETNAMES(' + (Math.floor(Math.random() * 500) + 1) + ')'
);
}
如前所述:
除非参数更改,否则自定义函数不会更新。
可能的解决方案是在单个单元格中创建一个复选框,并将此单元格用作自定义函数的参数:
=myFunction(A1)
使用谷歌财务函数作为参数。喜欢 =GOOGLEFINANCE("CURRENCY:CADARS")
这些功能强制每 x 分钟重新加载一次
按原样处理 Lexi 的脚本,它似乎不再适用于当前的表格,但如果我将虚拟变量作为参数添加到我的函数中(不需要在函数中实际使用它),它确实会强制谷歌表格再次刷新页面。
因此,声明如下: function myFunction(firstParam,dummy) 然后调用它就像建议的那样。这对我有用。
此外,如果随机变量出现在您编辑的所有工作表上是一件令人讨厌的事情,那么限制为一张工作表的简单补救措施如下:
function onEdit(e)
{
e.source.getSheetByName('THESHEETNAME').getRange('J1').setValue(Math.random());
}
我在函数中使用了一个虚拟变量,这个变量指的是电子表格中的一个单元格。然后我有一个在该单元格中写入一个数字的Myfunction()
脚本。Math.Random
MyFunction
在触发服务(编辑/当前项目触发器)下,您可以选择不同的事件触发器,例如 On-Open 或时间驱动,您可以选择例如一个时间段,从 1 分钟到一个月。
缓存问题的另一种解决方案。
在您的方法中有一个虚拟变量。经过
Filter(<the cell or cell range>,1=1)
作为该参数的值。
例如
=getValueScript("B1","B4:Z10", filter(B4:Z10,1=1))
不使用过滤器的输出。但是它向电子表格表明该公式对 B4:Z10 范围敏感。
我在为工作创建仪表板时遇到了类似的问题。尽管 Arsen 最近发表了评论,Chamil 的上述解决方案(即使用 Sheet 的 Filter 函数作为值传递给函数中的虚拟变量)工作得很好。在我的例子中,我使用一个函数来监视一个范围,并且不能在同一范围上使用过滤器,因为它创建了一个循环引用。所以我只有一个单元格(在我的例子中是下面代码中的 E45),只要我想更新我的函数,我就在其中更改了数字:
=myFunction("E3:E43","D44",filter(E45,1=1))
正如 Chamil 所指出的,脚本中没有使用过滤器:
function myFunction(range, colorRef, dummy) {
variable 'dummy' not used in code here
}
由于 google app 脚本是 JS 的扩展,因此函数应该能够处理比函数签名中定义的参数更多或更少的参数。所以如果你有一些功能,比如
function ADD(a, b) {
return CONSTANTS!$A$1 + a + b
}
然后你会称这个函数为
=ADD(A1, B1, $A$2)
其中 $A$2 是一些复选框(插入 -> 复选框),您可以在需要更改工作表和单元格 CONSTANTS$A$1 中的值后单击以“刷新”
您可以做的是在电子表格的某处设置另一个单元格,每次添加新工作表时都会更新该单元格。确保它不会因每次更改而更新,而仅在您想要进行计算时更新(在您添加工作表时)。然后,将此单元格的引用传递给您的自定义函数。如前所述,自定义函数可以忽略此参数。
鉴于 Henrique Abreu 解释的该功能,您可以尝试开箱即用的电子表格功能QUERY,喜欢 SQL 的查询是我在处理原始数据时经常使用的,并将数据作为摘要获取到不同的选项卡,结果数据会更新实时跟踪原始数据的变化。
我的建议是基于这样一个事实,即您的脚本没有 URL 获取等高级工作,只是数据工作,因为没有实际读取数据,我无法使用 QUERY 给出精确的解决方案。
关于 Henrique Abreu 提到的缓存功能(我没有足够的声誉直接在他的回答下发表评论),我做了测试,发现:
看起来没有缓存工作,测试函数的脚本如下所示:
函数加法器(基础){ Utilities.sleep(5000); 返回基数 + 10;}
通过调用一个单元格在工作表中应用该自定义函数 adder(),然后来回更改该单元格值,每次我看到加载消息和总时间超过 5 秒。它可能与此GAS 问题中提到的更新有关:
此问题现已修复。新表格中的自定义函数现在可以感知上下文,并且不会像以前那样积极地缓存值。
我不想有一个虚拟参数。YMMV 关于这一点。
1 一个单元格是“项目列表”,一个是“刷新”
2 如果单元格为“刷新”,则使用“onEdit”编写脚本:
a)清空文档缓存
b)用外部数据填充文档缓存(在我的例子中是一个表)
c) 对于所有具有我的 'getStockoData(...' 自定义函数的单元格
得到公式
设置'= 0'
设置公式
d) 将 (1) 中的单元格设置为“Ready”
这确实会刷新您想要的位,但速度不快。
您应该使用特定的更新函数,初始化“当前时间”变量并将这个永久更新的变量传递给您的自定义函数。然后转到触发器并为更新功能设置“每分钟”时间驱动触发器(或选择另一个更新时间间隔)。
编码:
function update() {
var dt = new Date();
var ts = dt.toLocaleTimeString();
var cellVal = '=CustomFunction("'+ ts + '")';
SpreadsheetApp.getActiveSheet().getRange('A1').setValue(cellVal);
}
今天我解决了这个问题
function MY_FUNC(a, b, additional_param) { /* ... */ }
=MY_FUNC("a", "b", A1)
现在,只需单击一下(在复选框上)即可强制重新计算我的函数。
正如@Brionius 所说,在函数上添加一个额外的动态参数。如果您使用 now() 您可能会遇到超时问题,使更新速度变慢...
cell A1 = int(now()*1000)
cell A2 = function(args..., A1)
如果您编写了自定义函数并在电子表格中将其用作公式,则每次打开电子表格或修改任何引用单元格时,都会重新计算公式。
如果您只想继续盯着电子表格并希望其值发生变化,请考虑添加一个定时触发器来更新单元格。在此处阅读有关触发器的更多信息