経緯
GASで外部APIを叩くときUrlFetchAppを一般的に用いるが、これのリトライ処理をする場合、try-catchで囲んでUtilities.sleep()して〜と書いていると、コードが長くなってしまうので、JavascriptのProxyで効率化してみました。
コード
function createRetryProxy(target, maxRetries = 3, waitMs = 1000) {
return new Proxy(target, {
get(target, prop) {
const original = target[prop];
if (typeof original !== 'function') {
return original;
}
return (...args) => {
let cnt = 0;
while (cnt < maxRetries) {
try {
return original.apply(target, args);
} catch (e) {
cnt++;
console.warn(`Proxy Warn: ${prop.toString()} (${cnt}/${maxRetries}): ${e.message}`);
if (cnt > maxRetries) {
console.error(`The maximum number of retries has been reached.`);
throw e;
}
Utilities.sleep(waitMs * cnt);
}
}
};
}
});
}
function testRetryProxy() {
const WrappedUrlFetchApp = createRetryProxy(UrlFetchApp, 3, 2000);
try {
// DNSエラー
const response = WrappedUrlFetchApp.fetch("http://localhost:8080");
console.log(response.getContentText());
} catch (e) {
console.log(e.message);
}
}
簡単な解説
Proxyオブジェクトを生成すると、元のオブジェクトへの操作が行われたとき、操作を傍受したり動作を再定義することができます。get()でトラップして処理を行い、apply()で本物の処理を行う、という流れです。
応用
オブジェクトをProxyでラップして、プロパティの代入時にsetでスプレッドシートに書き込んで、プロパティの読み取り時にgetでスプレッドシートから読み込むといったこともできます。こうすることで、GAS特有の.getRange().setValue()のような長いコードではなく、変数代入のような気軽さでシート操作ができたりします。
function reactiveCell() {
const spr = SpreadsheetApp.openById("sheetid");
const sheet = spr.getSheetByName("sheetname");
const cells = new Proxy({}, {
get(target, prop) {
return sheet.getRange(prop).getValue();
},
set(target, prop, value) {
sheet.getRange(prop).setValue(value);
return true;
}
});
cells.A1 = 42;
cells.A2 = "hoge";
console.log(`A1 is ${cells.A1}`);
}
おわりに
スパゲッティコードに悩んでるGAS書きの皆様は、ぜひProxyのことを頭の片隅に入れてGASをガスガス書いて欲しいです。
