#google-apps-script #google-sheets #web-applications #global-variables
Вопрос:
Я пытался получить некоторые функции в скрипте Google apps (внутри электронной таблицы) для изменения глобальной переменной, но, похоже, не могу этого понять.
В принципе, я хочу объявить переменную (в данном случае «globalTestVar»), и каждый раз, когда запускается одна из двух функций (globalVarTestFunctionOne и две), эта переменная должна увеличиваться на единицу.
Проблема в том, что переменная объявляется снова каждый раз при нажатии кнопки, даже если об этом должен позаботиться оператор if(typeof(globalTestVar) == ‘undefined’).
Я привык к Objective C и Java, где я могу объявлять свои переменные в начале и изменять эти переменные в любом месте кода.
Извините, если это основной вопрос, но я уже несколько часов гуглю и просто не могу заставить его работать.
Вот код:
logstuff("outside");
function logstuff(logInput){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow() 1;
sheet.getRange("A" lastRow).setValue(logInput);
return;
}
if (typeof(globalTestVar) == 'undefined') {
logstuff('declaring global variable');
globalTestVar = 0;
} else {
logstuff('global variable has been declared');
}
function globalVarTestUIFunction() {
var app = UiApp.createApplication().setTitle('Test UI');
var doc = SpreadsheetApp.getActive();
var formPanel = app.createVerticalPanel();
var buttonF1 = app.createButton('F1');
var buttonbuttonF1ClickHandler = app.createServerClickHandler("globalVarTestFunctionOne");
buttonF1.addClickHandler(buttonbuttonF1ClickHandler);
buttonbuttonF1ClickHandler.addCallbackElement(formPanel);
var buttonF2 = app.createButton('F2');
var buttonbuttonF2ClickHandler = app.createServerClickHandler("globalVarTestFunctionTwo");
buttonF2.addClickHandler(buttonbuttonF2ClickHandler);
buttonbuttonF2ClickHandler.addCallbackElement(formPanel);
app.add(formPanel);
formPanel.add(buttonF1);
formPanel.add(buttonF2);
doc.show(app);
return app;
}
function globalVarTestFunctionOne() {
logstuff('globalVarTestFunctionOne');
globalTestVar ;
logstuff('Value of globalTestVar: ' globalTestVar);
}
function globalVarTestFunctionTwo() {
logstuff('globalVarTestFunctionTwo');
globalTestVar ;
logstuff('Value of globalTestVar: ' globalTestVar);
}
Выход:
- снаружи 3
- объявление глобальной переменной
- снаружи 3
- объявление глобальной переменной
- globalvartestфункцион
- Значение globalTestVar: 1
- снаружи 3
- объявление глобальной переменной
- Globalvartestфункция два
- Значение globalTestVar: 1
Я написал свою собственную функцию «logstuff» для распечатки сообщений, потому что мне не нравится встроенная функция Logger.log.
Спасибо!
Ответ №1:
Вам это не понравится: глобальные переменные в GAS статичны — вы не можете обновлять их и ожидать, что они сохранят свои значения. Я тоже гуглил это несколько часов.
Вы можете использовать CacheService
или ScriptDB
как возможное хранилище для такого рода проблем. CacheService
является быстрым и простым в использовании, но ограниченным, потому что срок действия кэша в конечном итоге истечет. Я не пробовал ScriptDB
Комментарии:
1. Что ж, это тревожит. Должен быть способ использовать какие-то глобальные переменные. Я подумал о обходном пути и использовал лист «Переменные»для сохранения/изменения и получения глобальных переменных. Но это не очень элегантно и не очень эффективно.
2. Раньше я
CacheService
делал то, что мне было нужно, но это было краткосрочное хранение для повышения производительности. Возможно, вы найдетеScriptDB
что-то получше, но я этого не пробовал.3. Да, кэш-сервис хорошо работает для строк и чисел, но возможно ли хранить массивы с помощью кэш-сервиса?
4. Нет — вы не можете хранить массивы в кэше, и что еще хуже:
JSON.stringify()
похоже, что он не возвращает строку JSON и также не сообщает об ошибке. Однако вы можете хранить массивы в базе данных ScriptDB. Будет ли это лучше, чем хранить их в виде диапазонов на листе, будет зависеть от того, что вы с ними делаете.5. Ребята, это не совсем то, что происходит. Глобальные значения не статичны , они глобальны , что относится к их области применения. В контексте выполнения глобальная переменная доступна для всех блоков кода. Когда вы нажимаете кнопку в клиентском скрипте (UiApp, HtmlService), создается новый контекст на стороне сервера для запуска соответствующего обработчика, и этот новый контекст имеет уникальный набор «глобальных» переменных. Строго говоря, ваша переменная не «повторно объявлена», и сравнение с «неопределенной» означает только то, что ей не присвоено значение в этом контексте .
Ответ №2:
Сервис свойств -> Свойства
В настоящее время (2015) я думаю, что лучше всего использовать службу свойств класса и свойства возвращаемого типа.
О глобальном масштабе
Насколько я понимаю, каждый новый вызов функции сценария (триггеры времени, пользователь щелкнул пункт меню, нажал кнопку и т. Д.) Приведет к новому полному анализу сценария без памяти о предыдущих исполнениях, если они каким-либо образом не сохранялись (например, в диапазоне электронных таблиц или с использованием Properties
).
Ответ №3:
Несмотря на то, что кэш-сервис будет работать, у него есть 6 часов максимального срока службы. Это можно решить, используя вместо этого службу свойств, как упоминал @consideRatio.
Примером оболочки может быть(вводит переменные в глобальный контекст)
/* Wrap variable inside global context */
const Globals = {
global:this,
items:{},
/* Can be 'User', 'Script', or 'Document'
** Script - same values for all executions of this script
** User - same values for all executions by same user
** Document - same values for any user for same document
** Check reference for details.
** https://developers.google.com/apps-script/guides/properties
**
*/
context:'Script',
/* Get property service based on requested context */
get service() {
return PropertiesService['get' this.context 'Properties']()
},
/* Assign implementation */
set(name, value = null) {
this.service.setProperty(name, JSON.stringify(value));
return value;
},
/* Read implementation */
get(name) {
var value = this.service.getProperty(name);
return value !== null? JSON.parse(value) : null;
},
/* Shortcut for setter of complex objects */
save(name) {
this.set(name, this.items[name]);
},
/* Save all */
flush(name) {
Object.keys(this.items).map(name => this.save(name));
},
/* Delete implementation */
reset(name) {
this.service.deleteProperty(name);
delete this.items[name];
},
/* Add to global scope */
init(name, default_value = null) {
if(! this.items.hasOwnProperty(name)) {
if(this.service.getProperty(name) === null)
this.set(name, default_value);
this.items[name] = this.get(name);
Object.defineProperty(this.global, name, {
get: () => {return this.items[name]},
set: (value) => {return this.items[name] = this.set(name, value)},
})
}
return this.items[name];
}
}
После регистрации в Globals.init переменные можно использовать так же, как обычные переменные. Это работает с примитивами, однако, поскольку наблюдатели не поддерживаются для сложных объектов, они должны быть сброшены в конце сценария или явно.
/* In case you need to start over */
function restart_simulations() {
Globals.reset('counter');
Globals.reset('state');
test_run();
}
function test_run() {
/* After running init once, you can use global var as simple variable */
Globals.init('counter', 1); // Required to use "counter" var directly, as simple variable
/* Complex objects are also accepted */
Globals.init('state', { logined: false, items: [] });
/* Using primitives is simple */
Logger.log('Counter was ' counter);
counter = counter 1;
Logger.log('Counter is now ' counter);
/* Let's modify complex object */
Logger.log('State was ' JSON.stringify(state));
state.items.push(state.logined ? 'foo' : 'bar');
state.logined = ! state.logined;
Logger.log('State is now ' JSON.stringify(state));
/* Unfortunately, watchers aren't supported. Non-primitives have to be flushed */
/* Either explicitly */
//Globals.save('state');
/* Or all-at-once, e.g. on script end */
Globals.flush();
}
Вот что сохранилось среди различных 3-х пробежек
First run:
[20-10-29 06:13:17:463 EET] Counter was 1
[20-10-29 06:13:17:518 EET] Counter is now 2
[20-10-29 06:13:17:520 EET] State was {"logined":false,"items":[]}
[20-10-29 06:13:17:523 EET] State is now {"logined":true,"items":["bar"]}
Second run:
[20-10-29 06:13:43:162 EET] Counter was 2
[20-10-29 06:13:43:215 EET] Counter is now 3
[20-10-29 06:13:43:217 EET] State was {"logined":true,"items":["bar"]}
[20-10-29 06:13:43:218 EET] State is now {"logined":false,"items":["bar","foo"]}
Third run:
[20-10-29 06:14:22:817 EET] Counter was 3
[20-10-29 06:14:22:951 EET] Counter is now 4
[20-10-29 06:14:22:953 EET] State was {"logined":false,"items":["bar","foo"]}
[20-10-29 06:14:22:956 EET] State is now {"logined":true,"items":["bar","foo","bar"]}
Вы можете проверить рабочий пример здесь.