新玉ねぎっておいしいですよね。甘くって。
ただ、生で食べる時は、まあまあ薄くきらないと苦くって悲しくなりました。
今回は、みんな大好きGASの
「最大起動時間の壁を超える!」
の巻です。
GASの起動時間の最大値は?
リファレンスより、
「Consumer(多分無課金の人)のScript runtime」は 6minのようですね。
つまり、
一回の処理が6分を超えると、
「起動時間の最大値を超えました」
と表示され処理が途中で終わってしまいます。
(そもそも、6分以上の処理させんなよって感じなんですが、、、)
ググったら、回避方法を書いてくれてる方がおり、とても助かりました。
おかげで、わたしのバカ長い処理も何回かに分けて、
最後まで実行完了することができるようになりました。
参考:http://direction-note.com/gas-beyond-execution-time/
やり方
処理が5分を超える場合は、
トリガーを設定して、1分後に処理を続きからするようにする。
①for文のカウントなどを保持し、正しく続きから処理を再開するために、
GASのスクリプトプロパティでデータを保持させます。
スクリプトのプロパティとは?
ファイル > プロジェクトのプロパティ > スクリプトのプロパティ
にあり、キーバリューでデータを保持できます。

↑ キー:nowRow、バリュー:10のデータを保持させている。
この画面の「行を追加」ボタンよりデータを追加/削除することが可能。
GASにて追加することも可能
let properties = PropertiesService.getScriptProperties();
//スクリプトプロパティの登録
properties.setProperty(key, value);
//スクリプトプロパティのバリューを取得
properties.getProperty(key); //return value
②最大処理時間を超える前の5分で処理を中断し、
1分後にトリガーを設定する。
let properties = PropertiesService.getScriptProperties();
let targetArray = ['foo', 'bar', 'baz'....]; //この配列が膨大量の時に有効
/* メイン */
function mainSort() {
...
setOutputSheet();
}
/* 5分以上かかる可能性のある処理 */
function setOutputSheet(){
let startTime = new Date();
//スクリプトプロパティにトリガーIDを保存するときに使用するkey名
let triggerKey = "trigger";
...
for(let i = 0; i < targetArray.length; i++){
//開始時刻(startTime)と現時点の処理時点の時間を比較する
let diff = parseInt((new Date() - startTime) / (1000 * 60));
if(diff >= 5){
...
//トリガー(1分後)を登録する
setTrigger(triggerKey, "mainSort");
return;
}
//todo 5分以上かかる可能性のある処理
}
//全て実行終えたらトリガー不要なためを削除する
deleteTrigger(triggerKey);
}
/**
*指定したkeyに保存されているトリガーIDを使って、トリガーを削除する
*
* @param string triggerKey
* @return void
*/
function deleteTrigger(triggerKey) {
let triggerId = properties.getProperty(triggerKey);
if(!triggerId) return;
ScriptApp.getProjectTriggers().filter(function(trigger){
return trigger.getUniqueId() == triggerId;
})
.forEach(function(trigger) {
ScriptApp.deleteTrigger(trigger);
});
properties.deleteProperty(triggerKey);
}
/**
* トリガーを発行する
*
* @param string triggerKey
* @param string funcName
* @return void
*/
function setTrigger(triggerKey, funcName){
//既に同名で保存しているトリガーがあったら削除
deleteTrigger(triggerKey);
//1分後にトリガーを登録する
let date = new Date();
date.setMinutes(date.getMinutes() + 1);
let triggerId = ScriptApp.newTrigger(funcName).timeBased().at(date).create().getUniqueId();
Logger.log('setTrigger function_name "%s".', funcName);
//あとでトリガーを削除するために「スクリプトのプロパティ」にトリガーIDを保存しておく
properties.setProperty(triggerKey, triggerId);
}
作ってみたやつ
①②を組み合わせると、最大起動時間の壁を超えれるということです!
作ってみた全体はこんな感じです。
※このコード自体の処理は5分超えなかったのでsleep()とかして試してましたっw
こんな感じの横並びを、、、

↓
縦にソートします。

プレーヤーが日々追加されることを想定して、
一年の日数 × 最終的なプレーヤー人数分で縦に並ぶように作ってます。
コード全体(テキトーなので参考程度で! )
let properties = PropertiesService.getScriptProperties();
//★メイン
function mainSort() {
//プレーヤーマスター配列を作成
let playerMaster = getPlayerMaster();
let playerCount = Object.keys(playerMaster).length
//日付の配列を取得
let dataMaster = getDataMaster();
let dataCount = Object.keys(dataMaster).length
//日別結果データの取得
let DayDate = getDayDate(playerCount, dataCount);
//出力用シート(outputSheet)に日別結果の縦版の値を出力する。
setOutputSheet(playerMaster, dataMaster, DayDate);
}
/**
* 「No」「プレーヤー」を配列で取得する
*
* @return arrShopMaster = {
* 1: 'リュウ',
* 2: 'ケン'
* };
*/
function getPlayerMaster() {
let ABcolumnAll = inputSheet.getRange('A2:B101').getValues();
let LastRow = ABcolumnAll.filter(String).length;
let arrPlayerMaster = [];
//「-」を除く
for(let i = 0; i < LastRow; i++){
if(!ABcolumnAll[i][1].match(/\-/)){
arrPlayerMaster[ABcolumnAll[i][0]] = ABcolumnAll[i][1];
}
}
return arrPlayerMaster;
}
/**
* 日付を配列で取得する
*
* @return array arrDataMaster
*/
function getDataMaster() {
let rowAll = inputSheet.getRange('C1:ND1').getValues();
let arrDataMaster = [];
for(let i = 0; i < yearSum; i++){
arrDataMaster[i] = rowAll[0][i];
}
return arrDataMaster;
}
/**
* 日付をキーにその日の結果を格納する。
*
* @param intger playerCount
* @param intger dataCount
* @return array arrRet
*/
function getDayDate(playerCount, dataCount)
{
let col = inputSheet.getRange('C2:NQ'+(playerCount+1)).getValues();
let arrRet = [];
for(let i = 0; i < playerCount; i++){
arrRet[i] = [];
for(let j = 0; j < dataCount; j++){
arrRet[i][j] = col[i][j];
}
}
return arrRet;
}
/**
* 5分以上かかる可能性のある処理
*
* @return void
*/
function setOutputSheet(playerMaster, dataMaster, DayDate){
//処理の開始時刻を取得
let startTime = new Date();
//sleep(300000); //意図的に(300秒/60秒=)5分スリープさせる
//途中経過保存用の変数
let rowKey = "nowRow"; //何行目まで処理したかを保存するときに使用するkey
let dateKey = "nowDate"; //yearSumが何行目まで処理したかを保存するkey
let playerCountKey = "nowPlayerCount"; //lastRowが何行目まで処理したかを保存するkey
let triggerKey = "trigger"; //トリガーIDを保存するときに使用するkey
let nowRow = parseInt(properties.getProperty(rowKey));
if(!nowRow) nowRow = 1; //初回処理の時
let lastRow = Object.keys(DayDate).length;
let now_i = 0;
let nowDate = parseInt(properties.getProperty(dateKey));
if(nowDate){
let now_i = nowDate;
properties.deleteProperty(dateKey);
}
for(let i = now_i; i < yearSum; i++){
let date = dataMaster[i];
let now_j = 0;
let nowPlayerCount = parseInt(properties.getProperty(playerCountKey));
if(nowPlayerCount){
let now_j = nowPlayerCount;
properties.deleteProperty(playerCountKey);
}
for(let j = now_j; j < lastRow; j++) {
//開始時刻(startTime)と現時点の処理時点の時間を比較する
let diff = parseInt((new Date() - startTime) / (1000 * 60));
if(diff >= 5){
//何行まで処理したかなどを「スクリプトのプリロパティ」に保存する
properties.setProperty(rowKey, nowRow);
properties.setProperty(dateKey, i);
properties.setProperty(playerCountKey, j);
//トリガー(1分後)を登録する
setTrigger(triggerKey, "mainSort");
return;
}
outputSheet.getRange(nowRow, 1).setValue(date);
outputSheet.getRange(nowRow, 2).setValue(playerMaster[j+1]);
outputSheet.getRange(nowRow, 3).setValue(DayDate[j][i]);
nowRow++;
}
}
//全て実行終えたらトリガーや何行目まで実行したかなどのデータは
//不要なためを削除する
deleteTrigger(triggerKey);
properties.deleteProperty(rowKey);
properties.deleteProperty(dateKey);
properties.deleteProperty(playerCountKey);
}
/**
* 指定したkeyに保存されているトリガーIDを使って、トリガーを削除する
*
* @param string triggerKey
* @return void
*/
function deleteTrigger(triggerKey) {
let triggerId = properties.getProperty(triggerKey);
if(!triggerId) return;
ScriptApp.getProjectTriggers().filter(function(trigger){
return trigger.getUniqueId() == triggerId;
})
.forEach(function(trigger) {
ScriptApp.deleteTrigger(trigger);
});
properties.deleteProperty(triggerKey);
}
/**
* トリガーを発行。トリガーを発行した箇所から
*
* @param string triggerKey
* @param string funcName
* @return void
*/
function setTrigger(triggerKey, funcName){
//既に同じ名前で保存しているトリガーがあったら削除
deleteTrigger(triggerKey);
let date = new Date();
date.setMinutes(date.getMinutes() + 1); //1分後に再実行
let triggerId = ScriptApp.newTrigger(funcName).timeBased().at(date).create().getUniqueId();
Logger.log('setTrigger function_name "%s".', funcName);
//あとでトリガーを削除するために「スクリプトのプロパティ」にトリガーIDを保存しておくproperties.setProperty(triggerKey, triggerId);
}