既知の問題(再掲)
前回の投稿
にて、以下2つの問題がありました。
今回は、この2つを解決しようと思います。
問題1
UrlFetchApp.fetchAll
メソッドに引き渡すリクエスト数が多いと、以下のようなエラーが起こります。
問題2
検証の結果、同時実行数が多いと、レスポンスの一部に以下のようなHTMLが返ってきます。
問題1の解決策
この問題の解決策としては、リクエストを100個ずつに分割して、fetchAll ()
メソッドに渡す方法が良いでしょう。
と、いうことで、chunkRequest()
関数を作ることにします。
この関数は、リクエスト配列を指定された数で分割し、分割後のリクエスト配列を要素とした配列を返します。
/**
* リクエスト配列を指定された数で分割し、分割されたリクエスト配列を要素とした二次元配列を返す。
* @param {object[]} requests - 分割対象のリクエスト配列
* @param {number} chunkSize - 分割するサイズ
* @return {object[][]} - 分割されたリクエスト配列を要素とした二次元配列
*/
function chunkRequest(requests,chunkSize) {
let chunkedRequests = [];
// whileループで元の配列から要素を取り出す。
while(requests.length > 0) {
chunkedRequests.push(requests.splice(0, chunkSize));
}
return chunkedRequests;
}
問題2の解決策
この問題の解決策としては、再帰関数を利用すると良いでしょう。
と、いうことで、fetchParaRequests()
関数を作ることにします。
この関数は、UrlFetchApp.fetchAll()
メソッドを実行し、レスポンス内に「同時に実行中のスクリプト」が含まれていたら、再実行用のリクエスト配列に、エラーが出たリクエストを追加します。
/**
* リクエスト配列を、fetchAll()メソッドを実行し、レスポンス内に、「同時実行数が多い」という
* エラーが出たリクエストは、再実行用リクエスト配列に追加し、再実行用リクエスト配列を引渡し
* 再帰実行する。
* その他のレスポンスは、レスポンスの配列に追加され、関数終了時に返す。
*
* @param {object[]} requests - 並列実行させるリクエストの配列
* @param {number} count - 再帰実行カウント用。呼び出し時は、0を引き渡す。
* @return {object[]} - レスポンスを要素とした配列
*/
function fetchParaRequests(requests,count) {
let reRequests = [];
let reses = [];
Logger.log('再帰実行カウント: ' + count);
count++;
const res = UrlFetchApp.fetchAll(requests);
for (let i = 0; i < res.length; i++) {
if (res[i].getContentText().includes('同時に実行中のスクリプト')) {
//レスポンス内に「同時に実行中のスクリプト」という文字が含まれていた場合(=同時実行数エラー)
Logger.log('再実行 要素No.' + i);
reRequests.push(requests[i]);//該当リクエストを再実行用配列に追加
} else {
reses.push(res[i]);//結果をレスポンス配列に追加
}
}
if (reRequests.length != 0) {
//再実行用配列の要素数が0でない(=再実行対象のリクエストがある。)
Utilities.sleep(10000);//10秒休んで
//再帰呼び出しする。再帰呼び出しの結果を reses に追加
reses = reses.concat(fetchParaRequests(reRequests,count));
}
return reses;
}
既知の問題に対応したメインルーチン
前回の投稿のコードを、書き換えます。
今回、既知の問題対応のため、chunkRequest()
関数と、再帰関数のfetchParaRequests()
関数を新たに作ったので、それを処理に組み込みましょう。
さらに、実行数の制限がなくなったので、30個の並列実行を、300個の並列実行にしてみます。
リクエストで送信する「age」が、最大334という、ありえない数になってしまいますが...(汗
function fetchPostPara300Req() {
//ウェブアプリを300個並列実行するサンプル
const fetchUrl='[ウェブアプリのURL]';
//DriveApp.getRootFolder().getName(); //API実行時に401エラーが出た場合はこれを実行すると良さそう
//APIを全体公開出来ない場合はトークンが必要。トークンをfetch直前に取得
//const token = ScriptApp.getOAuthToken(); //APIを全体公開出来ない場合この行が必要。
//リクエスト配列の生成
let requests = [];
for (let i = 0; i < 300; i++) {
//ウェブアプリに引き渡すデータ部分を生成。
let data = {
'name': 'Bob Smith',
'age': 35 + i, //すべて同じリクエストだと寂しいので少し変える。
'hobby' : '他人のソースコードのダメ出し。// : ? . % a',
'pets': ['fido', 'fluffy']
};
//リクエストのJavaScriptオブジェクトを作成
let request = {
'url': fetchUrl,
'method' : 'post',
'headers': {
'Content-Type': 'application/json'
//,'Authorization': `Bearer ${token}` //APIを全体公開出来ない場合この認証が必要
},
'payload' : JSON.stringify(data) //ウェブアプリに引き渡すデータ部分をJSON変換し、payloadに配置。
};
//リクエスト配列にリクエストのJavaScriptオブジェクト追加
requests.push(request);
}
//100個ずつのリクエストに分割
const chunkedRequests = chunkRequest(requests,100);
//100個ずつリクエストを送信
for (let j = 0; j < chunkedRequests.length; j++) {
const res = fetchParaRequests(chunkedRequests[j],0);
//レスポンスの確認
for (let k = 0; k < res.length; k++) {
Logger.log(res[k].getContentText());
}
}
}