【回避成功】スプレッドシートでGASのタイムアウトをトリガーで回避する方法

GASエラーの対処
この記事は約14分で読めます。

スプレッドシートのGASを使用して、
「起動時間の最大値を超えました」というエラー
ありますよね。

実行時間が6分を超えると
出てしまうエラーですが、

なるべく時間がかからないようなコーディングを行なったとしても、
6分というのは短すぎです・・・!

そこで、このエラーの壁を越えるべく、
試行錯誤の結果、
無事に回避することができたので

その方法について忘備録として書き残しています。

私の環境は、1つのコピー元の共有シートがあって、
その共有シートから使い人がそれぞれコピーして、
使用しているという状態で、

その元の共有シートを更新したときに、
同じようにコピーされたシートにも更新を反映させるために、
処理を実行しています。

GASのタイムアウト回避方法

私がタイムアウト回避するにあたって、
行なった施策は2つです。

1、タイムアウトのエラーが出る前にトリガーセット
2、並列起動して一気に処理をする

この記事では、トリガーセットで回避する方法について書いています。

並列実行で回避する方法はこちら
【回避成功】スプレッドシートでGASのタイムアウトを並列実行で回避する方法

こちらの記事を参考にさせていただきました。
GAS エラー「Exceeded maximum execution time」と戦った

1、タイムアウトのエラーが出る前にトリガーセット

これは、タイムアウトする前
4分あたりを目処に、
トリガーをセットするようにして、

処理が終了するまで、
一定時間をおいて、トリガーによって処理を継続させる
というものです。

ちょっとわかりにくいかもしれないですが

GASの実行には、
自分で実行ボタンで実行させる方法と、

時間や編集時などをトリガーにして、
自動実行させる方法の2つがあります。

この時間で自動実行させるトリガーを利用して、
終わっていない処理を終わらせてしまおう!
というのが、

トリガーセットによるタイムアウト回避です。

スポンサーリンク

具体的には、まず処理を行いたいコピー元シートと、
編集を行いたいコピー先のシートを一覧にした、
メンテナンス用のブックを用意します。

スプレッドシート GAS メンテブック

B列に処理を行いたい元となるブックのIDと、コピーをしたい編集先のブックのIDを記入しておきます。
C列は、処理が完了したらチェックが入るようにGASでコーディングしています。

E列には、更新を行いたいシート名を記入して、
F列にそのシートの更新範囲を記入しておきます。

基本的に上から順番にブックIDを読み込んでいき、全てのシートの処理が終わったらチェックして次へ
という単純な動きをしていくだけです。

こんな感じで作成をしたら、GASのコードを書いていきます。

function mente() {
    var mentbook = '読み込むメンテナンスブックID';
    var mentSht = SpreadsheetApp.openById(book)
  .getSheetByName('シート1');//メンテナンスブックのシート名

    //経過時間計測開始
    const startTime = new Date();

        //トリガーセットするトリガー名(削除の時に使用)
      var functionName = 'mente';

        //トリガーセット確認用
        var trigSet = 0;

    //完了チェックの開始行
    var n2 = 3;

    //コピー元のブックID取得
    var mstbook = mentSht.getRange(2,2).getValue();

    for(let n = 3; n <= 999; n++){ //n=シートID開始行数<=ラストID列数

        var BookId = mentSht.getRange(n,2).getValue(); //行,列@コピー元を抜いてその下行から

        for(let i = 3; i <= 999; i++){ //メンテシート開始行<=ラスト行 //タイムアウト回避用 var comp = mentSht.getRange(n2,3).getValue(); //完了チェックがされていたら処理を抜ける if(comp == true){ break; } Logger.log(BookID+'ブックのアプデ開始'); //ここに行いたい処理 var sShtName = mentSht.getRange(i,5).getValue(); //処理したいシート名取得 var mentRange = mentSht.getRange(i,6).getValue(); //更新する範囲 Logger.log(BookID+'ブック「'+sShtName+'」シート処理'); //コピー先ブックIDの読み込み var masterSht = SpreadsheetApp.openById(mstbook) .getSheetByName(sShtName); //コピー先のブック読み込み var targetSht = SpreadsheetApp.openById(BookId); var targetShtname = targetSht.getSheetByName(sShtName); //Logger.log(targetShtname+'ターゲットシートネーム') //マスタシートをターゲット先のシートにコピー var copySht = masterSht.copyTo(targetSht); /ターゲットのシート名とRangeを指定してコピー実行 copySht.getRange(mentRange).copyTo(targetShtname.getRange(mentRange)); let n = i + 1; var sShtName2 = mentSht.getRange(n,2).getValue(); if(sShtName2 == ''){ Logger.log('処理シート完了'); break; }//次のブックIDが空白だったら処理を抜ける //コピーシートを削除 targetSht.deleteSheet(copySht); Logger.log(BookN+'ブック「'+sShtName+'」コピーシート削除'); //完了チェック if(n == n2){ mentSht.getRange(n,3).setValue('true'); n2++; } //実行開始から4分を過ぎていたらタイムアウト回避のためトリガーをセット //実行終了時間計測 const endTime = new Date(); //実行時間の計算 const runTime = (endTime - startTime) / (1000 * 60); if(runTime > 4){
            Logger.log(runTime+'経過:タイムアウト');
            trigSet = 1;
            //scriptProperties.setProperty();
            ScriptApp.newTrigger('mente')
                        .timeBased()
                        .after(1 * 60 * 1000)
                        .create();
            Logger.log('トリガーセット完了');
            break;
            }

            let x = n+1;
            var BookId2 = mentSht.getRange(x,2).getValue(); 

            if(BookId2 === ''){
            Logger.log('処理対象ブック完了');
            break;
            }//ブックIDの次の行が空白だったら処理を抜ける

            //処理が完了時にトリガーが残っていたらトリガー削除
            if(trigSet === 0){
                var triggers = ScriptApp.getProjectTriggers();
                for (var i = 0; i < triggers.length; i++) {
                    // gets a script trigger
                    var trigger = triggers[i];
                    
                    // gets the function that will be called when the trigger fires
                    if (trigger.getHandlerFunction() == functionName) {
                        // removes the given trigger so it no longer runs
                        ScriptApp.deleteTrigger(trigger);
                    }
                }
                Logger.log('トリガー削除完了');
            }
    }

タイムアウトのために行なっているコードは、最初の計測時間開始のこの部分と、

    //経過時間計測開始
    const startTime = new Date();

   //トリガーセットするトリガー名(削除の時に使用)
    var functionName = 'mente';

   //トリガーセット確認用
   var trigSet = 0;

実際にトリガーをセットしている部分、

            //実行開始から4分を過ぎていたらタイムアウト回避のためトリガーをセット
            //実行終了時間計測
            const endTime = new Date();
            //実行時間の計算
            const runTime = (endTime - startTime) / (1000 * 60);
            if(runTime > 4){
            Logger.log(runTime+'経過:タイムアウト');
            trigSet = 1;
            //scriptProperties.setProperty();
            ScriptApp.newTrigger('mente')
                        .timeBased()
                        .after(1 * 60 * 1000)
                        .create();
            Logger.log('トリガーセット完了');
            break;
            }

そして、トリガーが残っていたら削除している部分

            //処理が完了時にトリガーが残っていたらトリガー削除
            if(trigSet === 0){
                var triggers = ScriptApp.getProjectTriggers();
                for (var i = 0; i < triggers.length; i++) {
                    // gets a script trigger
                    var trigger = triggers[i];
                    
                    // gets the function that will be called when the trigger fires
                    if (trigger.getHandlerFunction() == functionName) {
                        // removes the given trigger so it no longer runs
                        ScriptApp.deleteTrigger(trigger);
                    }
                }
                Logger.log('トリガー削除完了');
            }

この3箇所になります。

これは、私が実際に使用している処理を含めたコードとなりますので、
応用できるように簡略化したものを紹介していきます。

タイムアウト回避に応用で使用できるコード

スポンサーリンク

ここからは、改変して使用しやすくコードをまとめて置いています。
コピペで使用できるので、やり方を見ながらコピペして使用してみてください。

GASタイムアウト回避、トリガーセットコピペ用

まず、function内のメイン処理を開始する手前に以下のコードをコピペ
※トリガー名はご使用のfunction名に変更してください(menteの部分)


    //経過時間計測開始
    const startTime = new Date();

   //トリガーセットするトリガー名(削除の時に使用)
   var functionName = 'mente';

   //トリガーセット確認用
   var trigSet = 0;

次に、処理の切りのいいところに次のコードをコピペ(このままだと変数を渡すことはできないので、変数を渡す必要がない部分がベスト)
※トリガー名はご使用のfunction名に変更してください(menteの部分)


            //実行開始から4分を過ぎていたらタイムアウト回避のためトリガーをセット
            //実行終了時間計測
            const endTime = new Date();
            //実行時間の計算
            const runTime = (endTime - startTime) / (1000 * 60);
            if(runTime > 4){
            Logger.log(runTime+'経過:タイムアウト');
            trigSet = 1;
            //scriptProperties.setProperty();
            ScriptApp.newTrigger('mente')
                        .timeBased()
                        .after(1 * 60 * 1000)
                        .create();
            Logger.log('トリガーセット完了');
            break;
            }

最後に、処理が完了して、functionを閉じる直前に以下のコードをコピペ


            //処理が完了時にトリガーが残っていたらトリガー削除
            if(trigSet === 0){
                var triggers = ScriptApp.getProjectTriggers();
                for (var i = 0; i < triggers.length; i++) {
                    // gets a script trigger
                    var trigger = triggers[i];
                    
                    // gets the function that will be called when the trigger fires
                    if (trigger.getHandlerFunction() == functionName) {
                        // removes the given trigger so it no longer runs
                        ScriptApp.deleteTrigger(trigger);
                    }
                }
                Logger.log('トリガー削除完了');
            }

これで、処理開始から4分以上経っていた場合、
トリガーをセットして一定時間ごとにトリガーが起動して実行されるようになります。

ただ、コピペだけだと、終了の処理が入っていないので、
終了したら何かアクションして、確認してBreak等の、処理を抜けたり、終了するコードを入れる必要があります。

応用でコピペするには、ある程度GASの知識が必要にはなりますが、
そんなに難しくはないので、ぜひ取り入れて見てください!

スポンサーリンク

コメント

タイトルとURLをコピーしました