iketomosan
@iketomosan

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

GASでタイムアウトエラーを回避したい

Q&A

解決したいこと

タイムアウトエラーを回避したいです。
以下のコードを作成したのですが、正しく動かず難儀しております。


スプレッドシートにて、アイテム所持数シートと各キャラクターチェックシートのアイテム数を比較し、アイテムが足りていたら条件付き書式で#4285f4でセルに色を塗る。

その後GASで一行ずつ「データ入っている場所、色が塗ってない場所」を探し、あれば終わり
なければ(材料がすべて足りている状態だったら)制作可能の◯をつける。

連続処理時間の上限が5分半なので、5分経過した段階で一度処理を終了し続きから処理を再開するようにしたいです。

その、次の処理が始まらずに困っています。

発生している問題・エラー

出ているエラーメッセージを入力
エラーメッセージ等は出ておらず、処理時間の限界で
処理が途中で止まってしまう状態です。

該当するソースコード

function MaterialCheck() {
  let startTime = new Date();

  //各ページを取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetS = ss.getSheetByName("アイテム所持数")
  const sheetM = ss.getSheetByName("必要素材シート1")
  const sheetK = ss.getSheetByName("必要素材シート2")
  const sheetT = ss.getSheetByName("必要素材シート3")
  let charaSheet = [sheetM,sheetT,sheetK]

  //所持素材リストのどの行を使うか
  let charaRows = [2,3,4]
  let creatCheck = new Array()
  let rowCheck = new Array()

  let startPage = Number(PropertiesService.getScriptProperties().getProperty('nextr'));
  let startRow = Number(PropertiesService.getScriptProperties().getProperty('nexti'));
  if (!startRow) startRow = 4; // もしstartIndexがnullの場合は1を代入
  if (!startPage) startPage = 0; // もしstartIndexがnullの場合は1を代入


  for(let r = startPage; r <= 3; r++){//むくたいかきのページと行の切り替え
        for(i = startRow; i <= charaSheet[r].getLastRow(); i++){
      Logger.log(i)
      Logger.log(r)
     
      charaSheet[r].getRange(i,1).setBackground("#FFFFFF")
      let currentTime = new Date(); // ②ループx周目時点の日時
      let seconds = (currentTime - startTime)/1000; // 経過秒数を計算(①と②の差分)

       if(charaSheet[r].getRange(i,1).getValue() != "⚫︎"){ //左に⚫︎が入ってる場合行をスルー
      for(j = 3; j <= charaSheet[r].getLastColumn(); j++){

          if(charaSheet[r].getRange(i,j).getValue() != "" && charaSheet[r].getRange(i,j).getBackground() != "#4285f4"){
            creatCheck[i - 1] = ""
            break
          }else if(j >= charaSheet[r].getLastColumn()){
            creatCheck[i - 1] = "◯"

          }
        }
      }else{
        creatCheck[i - 1] = "⚫︎"
      }

      if(seconds > 300){
        // 次回のチェック対象
        let nextPage = r;
        let nextRow = i + 1;
        if (nextRow > charaSheet[r].getLastRow()) {
          nextPage++;
          nextRow = 4;
        }

        // トリガーを設定
        PropertiesService.getScriptProperties().setProperty('nextr', nextPage);
        PropertiesService.getScriptProperties().setProperty('nexti', nextRow);
        setTrigger();

        // 300秒経過したらreturnして強制的に実行を中断する
        return;
      }
    }
    startRow = 4
  }

  // 全ての素材をチェック完了
  Logger.log('全ての素材をチェック完了しました。');
}


function setTrigger() {
  let triggers = ScriptApp.getProjectTriggers()
  for(let trigger of triggers){
    if(trigger.getHandlerFunction() == "MaterialCheck"){
      ScriptApp.deleteTrigger(trigger);
    }
  }

  // 1分後にトリガーをセット(1分 = 60秒 = 1秒*60 = 1000ミリ秒 * 60)
  ScriptApp.newTrigger("MaterialCheck").timeBased().after(1000 * 60).create();
}

自分で試したこと

・バグがある?と見たので8ランタイムを無効にしてみた
・コードを書き直す必要が出てきたので、断念
・画面上部の実行ボタンではなく、スプレッドシートの図形にスクリプトをつけてそこから実行してみる

0

3Answer

スプレッドシートはgetRange(i,j).getValueで読み込むとものすごく遅いことを
サンプル作って確かめた方が良いかと思います

手前味噌ですがGASでスプレッドシートの高速読み書きの記事を書いてますのでどうぞ

0Like

Comments

  1. @iketomosan

    Questioner

    vramさんご助言ありがとうございます。

    範囲を指定し、getvaluesで取得、という方法は検討していて自分の知識でも可能だったのですが、その一括取得した必要素材シートのセルと、アイテム所持数シートのセルを比較する方法が検索してみてもわからずでこのような方法に落ち着いています……。

    なにか参考になるようなサイト等ご存知でしたらご教示いただけませんでしょうか?

シートの内容を3次元配列に入れて

sample.js
 for(let r = startPage; r <= 3; r++){
  const iValues[r] = charaSheet[r].getRange(1, 1, charaSheet[r].getLastRow(), charaSheet[r].getLastColumn()).getValues()
}

こうやったら読み込めそうですがどうでしょうか?

sample.js
iValues[r][row][col]

参考の記事書いた時点でGAS始めて3日目なので構文間違いがあると思いますが
スプレッドシートをメモリに入れられればあとは楽勝ですよ

何件ぐらいのデータがあるのかわかりませんが
一度メモリにさえ入れれば他の処理は一瞬で終わると思いますよ
スプレッドシートに読み書きする速度が目に見えるほど遅いってだけです

0Like

Comments

  1. @iketomosan

    Questioner

    getvalueをforで回すのが問題なのであって、getvaluesで一括で取得しforでifを回すのは比較的軽いってことなんですね。

    ありがとうございます、ちょっと試してみます!

エラーメッセージ等は出ておらず

本当に出ていませんか?
実行数をひとつひとつ詳細開いて確認してます?

ScriptApp.newTrigger("MaterialCheck").timeBased().after(1000 * 60).create();

無限にトリガーを作ろうとしてますが、トリガー上限も考慮されていますか?

n時間なら兎も角、このコードではトリガー上限によって破堤(強制中断)するかと思われます。

0Like

Your answer might help someone💌