JS.39 GASで"maximum execution time"を超えて処理を行う。

2021-07-11(Sun) | tags: GAS

GASでプログラムを実行した際に、実行時間が "maximum execution time"ドキュメントによると6分のようです)を超えてしまいプログラムが全て終了しない(ループの途中で終了する)、という事態に陥ってしまいました。

そこで、最後まで終了しなかった場合に

  • どこまで処理したかをプロパティを介して引き継ぐ。
  • 再びプログラムを実行するために、新しいトリガーを設定する。

ことでプログラムを継続して実行し、何回かに分けて実行することで最後まで完了することを目指します。

※ なお、"GASの「起動時間の最大値を超えました」の壁を超えてみる!!!"を参考にさせていただきました。

変数定義

PropertiesService をコード簡略化のために global変数として定義します。

const prop = PropertiesService.getScriptProperties();

mainFunc

以下がメインとなる関数です。定期実行等をしたい場合は、この関数をトリガーに(手動で)設定してください。

// Main function.
function mainFunc(){
  LongTimeProcess(
    mainFuncName = "mainFunc",
    prop_triggerKey = "_tmp_trigger", // Holds the ID of the trigger set in the program.
    prop_curtIdx = "_tmp_curtIdx", // Holds the current idx. (indicates how much processing has been done.)
  )
}

LongTimeProcess

上記の関数でWrapしている、長い処理を実行する関数です。何かしらのリストを取得した後、for-loopで各データを取り扱います。(※ リスト取得にも時間がかかる場合、このリストをスプレッドシート等に保存する、などの対応が必要です。)

/** Long process that can take 6 minutes (more than "maximum execution time") or more
 * @param {string} mainFuncName The main (wrapper) function name.
 * @param {string} prop_triggerKey The keyname of the temporarily used property which holds the trigger Id which set in the program.
 * @param {string} prop_curtIdx The keyname of the temporarily used property which holds the idx which indicates how much processing has been done by the last process.
*/
function LongTimeProcess(mainFuncName, prop_triggerKey, prop_curtIdx){
  var startTime = new Date();
  // Get some list. (NOTE: If this process also takes time, it is also necessary
  // to save this data in a SpreadSheet or the like.)
  var some_list = [];
  // Last idx of the previous process
  var lastIdx = prop.getProperty(prop_curtIdx);
  lastIdx = lastIdx==undefined ? 0 : parseInd(lastIdx)
  for (var i=lastIdx; i<some_list.length; i++){

    // DO WHAT YOU WANT

    // If more than 330[s] have passed since the start.
    if ((new Date()-startTime)/1000 >= 330){
      setTrigger(mainFuncName, prop_triggerKey);
      // Store current information in properties.
      prop.setProperty(prop_curtIdx, i+1);
      return false;
    }
  }
  // When all the processes are finished, delete the temporarily saved data.
  deleteTrigger(prop_triggerKey);
  prop.deleteProperty(prop_curtIdx);
  return true;
}

setTrigger

途中までしか処理ができなかった場合、2分後に再び途中から実行させるために、トリガーを設定します。

/** Set a temporarily trigger
 * @param {string} mainFuncName The main (wrapper) function name.
 * @param {string} prop_triggerKey The keyname of the temporarily used property which holds the trigger Id which set in the program.
 */
function setTrigger(prop_triggerKey, mainFuncName){
  // Delete a (past) trigger that has already been registered with the same name.
  deleteTrigger(prop_triggerKey)
  // Register the trigger after 2 minute.
  var date = new Date();
  date.setMinutes(date.getMinutes()+2);
  var triggerId = ScriptApp.newTrigger(mainFuncName).timeBased().at(date).create().getUniqueId();
  Logger.log('setTrigger function_name "%s".', mainFuncName);
  // Store the trigger ID in a property so you can delete it later.
  prop.setProperty(prop_triggerKey, triggerId);
}

deleteTrigger

プログラム(上記のsetTrigger)で作成したトリガーを削除します。

/**
 * Delete the trigger using the trigger ID saved in the specified key.
 * @param {string} prop_triggerKey The keyname of the temporarily used property which holds the trigger Id which set in the program.
 */
function deleteTrigger(prop_triggerKey) {
  var triggerId = prop.getProperty(prop_triggerKey);
  if (triggerId != undefined){
    ScriptApp.getProjectTriggers().filter(function(trigger){
      return trigger.getUniqueId() == triggerId;
    }).forEach(function(trigger) {
      ScriptApp.deleteTrigger(trigger);
    });
    prop.deleteProperty(prop_triggerKey);
  }
}
other contents
social