Глобальные переменные в Google Скрипте (электронная таблица)

#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"]}
 

Вы можете проверить рабочий пример здесь.

Демо-скрипт