フォームのデータを取得できませんでした。のエラーに対処した話

  • このエントリーをはてなブックマークに追加
  • LINEで送る
2024/3/12 追記

強引に発生確率を下げる方法について、2024年の記事で解説しています。
両記事ともある程度有効な手段かと思いますので、あわせてご参照ください。
フォームのデータを取得できませんでした。に対処したもう1つのシンプルな方法

0.はじめに

2022年12月頃から、Googleフォームの送信時トリガーで
以下のようなエラーが発生し不安定な状態となっているようです。
フォームのデータを取得できませんでした。のエラー画像
私の環境でも発生していたため、
一助になれればと思い対応した方法を記載しておきます。
※エラーそのものを発生させなくする方法ではありません。

 

1.何が起こったのか

フォーム送信時トリガーを設定した時、
送信時作成のイベントから中身を取り出そうとする段階でエラーを吐きます。

エラーメッセージ:Exception: フォームのデータを取得できませんでした。しばらくしてからもう一度お試しください。

ただ毎回起こるわけではなく、不定期に発生するのが厄介です。

2.根本解決の方法はあるのか

調査したところ、根本的に当エラーが発生しなくする方法は見つけられませんでした。
なので一旦「エラーが起きても問題ない形」をご提案します。

 

 

3.即時リトライを試みる方法

3-1.コード

//フォーム送信時トリガーを設定
function formRes(e) {
  try{  //通常処理
    const itemresponses = e.response.getItemResponses(); //ここで対象のエラーが発生する 
    const address = e.response.getRespondentEmail();
    main(itemresponses, address);
  }catch(e){
    //エラー時
    console.log(e);

    //対象のフォームを取得 
    const form = FormApp.getActiveForm();
    //対象フォームの回答を過去分から全て取得する
    const allItemresponses = form.getResponses();
    //最新分は配列末尾に入っているため最新分を取得 
    /** 精度重視であれば、allItemresponsesの中身をgetTimestamp()して日次比較、最新分を取得する方がベターでしょう。*/ 
    const recentResponse = allItemresponses[allItemresponses.length-1]; 
    
    //recentResponseは、tryの中のe.responseと似た扱いができる
    const itemresponses = recentResponse.getItemResponses(); 
    const address = recentResponse.getRespondentEmail();
    main(itemresponses, address); 
  }
}               
                 
function main(itemresponses, address){
  //フォーム回答を使って行いたい処理 
  //メール送信するなりチャットツールに通知するなりデータ加工して二次利用するなり
  console.log("main"); 
}

 

3-2.解説

エラーが起きるところをtryに入れ、該当のエラー発生時にcatchします。
catchしたら無理に送信時トリガーでのフォーム情報を使わず、
formオブジェクト.getResponses()で対象フォームの回答全件を取得します。
そこから最新の回答を絞り込み、本来処理に持ち込む形です。

 

3-3.デメリット

3-3-1.ほぼ同時に複数の回答があった場合の精度が保証できない

リトライ処理中に他の回答があった場合、
最新側を取得して処理に入ってしまう可能性があります。

 

3-3-2.即時でない

当然、本来のフォーム送信時トリガーよりは反応が遅くなってしまいます。

 

3-3-3.リトライ処理自体も失敗することがある

このコードをテストしている時、リトライ処理自体も1回エラーを吐きました。
Exception: Failed to retrieve form data. Please wait and try again.
失敗した直後のcatchでリトライ処理を試みると、
結構な確率でFormにアクセスする段階でエラーを吐く印象です。
「リトライ処理自体は失敗しても5回まで繰り返す」等の対策があった方が良さそうです。

 

3-4.類似の方法

似ている方法として、フォーム回答を蓄積するスプレッドシートにGASを仕込み、
シート更新に応じて最新データを取得、処理する方法があります。

デメリットとしては、フォームの設問内容を追加/削除した際、
過去の質問項目もシートに残るため、正確に情報を抜き出すことが難しそうです。
「回答があったよ!」等の情報だけが必要であれば、これで良いかもしれませんね。

4.日次で処理漏れを検知、再実行する方法

4.1コード

//フォーム送信時トリガーを設定
function formRes(e) {
  try{
    //通常処理
    const itemresponses = e.response.getItemResponses();
    const address = e.response.getRespondentEmail();
    main(itemresponses, address);

    //ログシートを取得できれば方法は何でもいい
    const logSheet = SpreadsheetApp.openById("SSのID").getSheetByName("フォーム後処理ログ");
    const lastRow = logSheet.getLastRow();
    const responseId = e.response.getId();
    const timeStamp = e.response.getTimestamp();

    //ログ書き込み
    logSheet.getRange(lastRow+1,1,1,2).setValues([timeStamp, responseId]);

  }catch(e){
    //失敗時 一応タイムリーにエラーキャッチしたいならメール飛ばす等を実装    
  }
}

function main(itemresponses, address){
  //フォーム回答を使って行いたい処理 メール送信するなりチャットツールに通知するなりデータ加工して二次利用するなり
  console.log("main");
}

//昨日分の履行をチェックする 日次トリガーにする
function dailyCheck(){
  //昨日のdayjsオブジェクト
  const yesterday = dayjs.dayjs().subtract(1,"day");

  //昨日の回答を全件取得する
  //ライブラリdayjsを使用しているが、timestampが昨日であるかを確認できれば方法は何でもいい
  const form = FormApp.openById("フォームID");
  const yesterdayResponses = form.getResponses()
                            .filter(x => dayjs.dayjs(x.getTimestamp()).isSame(yesterday,"day"));
  //昨日のフォーム回答が0件だったらここで処理終了
  if(yesterdayResponses.length==0) return;

  //ログシートからログを全件取得し、空白を除外、A列に入力されているtimestampが昨日のものを抽出
  const logSheet = SpreadsheetApp.openById("シートID").getSheetByName("フォーム後処理ログ");
  const logs = logSheet.getRange("A1:A").getValues().filter(x => x)
                .filter(x => dayjs.dayjs(x[0]).isSame(yesterday,"day"));
  
  //ログシートの情報と、フォームから直接取得した昨日の回答全件を照らし合わせ、
  //ログシートに記載のないものを抽出する=送信時にtryが失敗していたものを抽出
  const targets = yesterdayResponses.filter(x => !logs.find(y => y[1]==x.response.getId())); 
  if(targets.length == 0) return;

  //メイン処理を実行する
  for(let target of targets){
    let itemresponses = target.getItemResponses();
    let address = target.getRespondentEmail();
    main(itemresponses, address);
  }
}

 

4-2.解説

仕組み自体が少々まどろっこしいかもしれません。ゆっくり読んでください。

 

4-2-1.実行ログを取る

まず、本来の送信時トリガーでの処理時に必ず実行ログを取るようにします。
発動時に取得するイベントオブジェクト(e)から、
「回答ID」を取得してログシートに入力する仕組みです。
※シートはどこでもいいですが、回答記録されるSS上にシートを作っておくと管理上楽でしょう。

 

4-2-2.ログシートの内容と、フォーム回答全件を比較する

さて、そうすると、当該エラーを吐いた時には「回答ID」はログには残りません。
この状況を利用します。

毎日0時~1時に、対象フォームの昨日分回答を全件取得して、
その内容とログシートの内容が一致していれば、
エラーは発生していなかったという事になります。
逆に一致していなければ、ログ記録未実行=失敗しているものがあります。

コードと一緒に見ていきましょう。

 

4-2-3.実行が漏れていた分の処理を実行する

エラーで処理が実行されていなかった分は、本来処理を改めて実行しましょう。
実行するための回答情報は、前項で取得した未実行分の「回答ID」を使って取得します。
コードですと、この部分になります。
フォーム送信時トリガーと扱いが同じオブジェクトを取得できちゃいます。

4-3.メリット・デメリット

4-3-1.【メリット】即時リトライよりは精度が上がる

ほぼ同時に複数の回答があった場合の、回答の取り違えが発生しません。
これは項3で紹介した方法より良い部分ですね。

 

4-3-2.【デメリット】回答からのタイムラグが大きい

今回のケースですと、分かりやすく日次トリガーを設定して
前日分の動作に問題が無かったかを検知しますので、遅いです。
午前9時にエラーを吐いた場合、その回答への対応は翌日になります。

フォームの用途上、タイムラグが許されない場合は
数時間毎に検知できるように工夫してみたり、
エラー時にメール等で通知し日中帯は即時フォローできるようにするなど、
工夫をすればある程度カバーできる問題かと思います。

 

4-3-3.【デメリット】日次処理自体が失敗する可能性がある

項3の方法で触れましたが、こちらでも似たような処理を実行しているため
何度か実行を試みるような処理を追加した方が無難だと思います。

 

4-3-4.【デメリット】処理が煩雑になる

最後はやっぱりこれに尽きますね。地味に面倒くさい。
「修正してくれ~」と思ってしまいますね。
ですが、GASやSaaS系ツールはサーバー側の問題で不安定になりますから、
それも見越して厚めのエラー処理を当たり前に仕込むべきなのかもしれませんね。

 

5.さいごに

いかがでしたでしょうか。
私はというと、2つ目の方法を採用して日次で管理しています。

ちなみに私の環境では送信時トリガーのフォームを20個ほど管理していますが、
昨年12月中旬からポツポツとエラーが発生し、2023/1/16以来発生していません。
皆さんはいかがでしょうか?よければコメントください。
⇒2023年2月現在、やはりたまに発生するようです。

もしかすると解消されたのかもしれませんが、
Google側が不安定になると同様の事が起こるかもしれません。
時間がある際に対処しておく事をおすすめします。

また、強引にエラーハンドリングを行う方法として他でも使えるかもしれません。
参考にしていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • LINEで送る

コメントを残す

*