1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS/JavaScript】重たい描画処理で「完了モーダル」が表示されない!UIブロックを回避するテクニック

Last updated at Posted at 2025-11-26

Google Apps Script (GAS) でWebアプリを開発していた際、「サーバー側の処理は終わっているのに、完了メッセージが表示されずに画面が固まる(ように見える)」 という現象に遭遇しました。

原因はJavaScriptの特性による「描画ブロック」でした。 今回は、その原因と setTimeout を使った解決策をメモとして残します。

🛑 発生した問題

GASの google.script.run を使い、以下のようなフローで処理を実装していました。

  1. 「処理中...」 というモーダル(プログレスバー)を表示。

  2. サーバー側で重たい計算処理を実行(数分かかる)。

  3. 処理完了後、サーバーから大量のデータを受け取る。

  4. モーダルの表示を 「完了!」 に切り替える。

  5. 受け取ったデータを元に、裏側の画面(巨大なテーブル)を 再描画 する。

しかし、実際に動かしてみると…

  • 期待: モーダルが「完了!」に変わる ➔ その後、裏の画面が更新される。

  • 現実: モーダルは「処理中...」のまま固まる ➔ 数秒後、いきなり画面が更新される(「完了!」を見る暇がない、あるいは表示されない)。

ユーザーからすると、「処理が終わったのか、フリーズしているのか分からない」 という不安な状態になっていました。

💻 修正前のコード(NG例)

JavaScript
google.script.run
  .withSuccessHandler(data => {
    // 1. モーダルを「完了」状態にする(軽い処理)
    updateModalToComplete("処理が完了しました");

    // 2. 画面全体を再描画する(重い処理)
    // ※ここで大量のDOM操作が発生
    renderHeavyTable(data); 
    
    // 3. 最後にローディングを解除
    setLoading(false);
  })
  .serverSideFunction();

🔍 原因:JavaScriptはシングルスレッド

JavaScriptは基本的に 「シングルスレッド(一人の作業員)」 で動いています。また、ブラウザの 「画面の描画(レンダリング)」 も、このスクリプト実行の合間に行われます。

上記のNGコードでは、以下のことが起きていました。

  1. updateModalToComplete で「完了」の文字に変更する命令を出す。
    👉 しかし、ブラウザはまだ描画しない(スクリプトの実行が忙しいため)。

  2. 休む間もなく renderHeavyTable(重い処理)が始まる。
    👉 ブラウザは全力で計算とDOM生成を行うため、画面更新がロックされる。

  3. スクリプトが全部終わって初めて、ブラウザは画面を描画する。
    👉 ユーザーには「処理中」から一気に「更新後の画面」に飛んだように見える。

つまり、「完了!」と表示する命令は出していたけれど、重い処理に邪魔されて、ユーザーの目に届く前に上書きされてしまったようです。

✅ 解決策

パターンA:setTimeout を使う方法(基本)

重たい描画処理を setTimeout で少しだけ遅らせることで、解決しました。

JavaScript
google.script.run
  .withSuccessHandler(data => {
    
    // 1. まずモーダルを「完了」にする(最優先)
    updateModalToComplete("処理が完了しました");

    // 2. 重たい処理を「非同期(後回し)」にする
    setTimeout(() => {
        try {
            // ここで重い描画処理を実行
            renderHeavyTable(data);
        } catch(e) {
            console.error(e);
        } finally {
            setLoading(false);
        }
    }, 100); // 0.1秒だけ待つ
    
  })
  .serverSideFunction();

💡 なぜこれで直るのか?

setTimeout(..., 100) を使うことで、処理の順番が以下のように変わります。

  1. JS: 「モーダルを完了に変えて!」と命令。

  2. JS: 「表の描画は後でやるから予約しとくわ(setTimeout)」と言って、一旦処理を終える。

  3. ブラウザ: 「お、スクリプトが一段落したな。じゃあ画面を更新しよう」 ➔ ここでモーダルが「完了」になる! ✅

  4. JS: 0.1秒後、予約されていた renderHeavyTable が発火し、裏側の表を作り始める。

たったこれだけで、ユーザー体験(UX)は劇的に向上しました。

[追記] コメントで頂いた「よりスマートな解決策」

記事公開後、コメントにて async/await を使った、より可読性の高い書き方 を教えていただきました。 GASの google.script.run.withSuccessHandler の仕様を活かした、ネスト(入れ子)が深くならない素晴らしい方法ですので紹介します。

パターンB:async/await を使う方法(発展)

withSuccessHandler に渡す関数は、GAS側ではその「返り値」を利用しません(何を返しても無視されます)。 この仕様を利用し、ハンドラ関数を async function(非同期関数)にしてしまうことで、setTimeout の待ち時間を 1行で書くことができます。

JavaScript
google.script.run
  .withSuccessHandler(async (data) => { // ★ ここに async をつける
    
    // 1. まずモーダルを「完了」にする
    updateModalToComplete("処理が完了しました");

    // 2. ここで0.1秒待つ (この間にブラウザが画面を描画する!)
    await new Promise(resolve => setTimeout(resolve, 100));

    // 3. 続きの処理 (入れ子にならず、そのまま下に書ける)
    try {
        renderHeavyTable(data);
    } catch(e) {
        console.error(e);
    } finally {
        setLoading(false);
    }

  })
  .serverSideFunction();

この書き方のメリット:

  1. 読みやすい: 「更新する」→「待つ」→「重い処理」という流れが、上から下へ一直線になるため直感的です。

  2. ネストが浅い: setTimeout(() => { ... }) のような入れ子がなくなるため、コードが右に寄っていかずスッキリします。

※ 注意点

async をつけた関数は自動的に Promise オブジェクトを返します。ライブラリや仕組みによってはコールバックの返り値で挙動が変わるもの(例:falseを返すと中断するなど)があります。返り値を使って挙動を変える仕組みの場合は、誤作動の原因になります。

📝 まとめ

  • JavaScriptで DOM操作(画面変更)をしても、即座に反映されるわけではない。
  • 直後に重い処理が続くと、画面の更新(再描画)がブロックされてしまう。
  • 「まずはユーザーに完了を伝える」 ことが重要な場合、その後の重い処理を setTimeout で逃がしてあげると、スムーズなUIになる。
  • 返り値が重要でない場合、async/await(パターンB)で記述すると、コードの可読性が上がる。

GASのWebアプリは一度に大量のデータを扱うことが多いため、このテクニックは必須級だと感じました。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?