0
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】location.reload()で画面が真っ白になる問題をasync/awaitで解決した話

Posted at

はじめに

Google Apps Script (GAS) でWebアプリを開発していた際、「サーバー側の処理は成功しているのに、location.reload()で画面を更新すると真っ白になる」という問題に遭遇しました。

この記事では、その原因とPromise/async-awaitを活用した解決策を自身のメモとして記録します。

🛑 発生した問題

症状

サーバー処理完了後にlocation.reload()を実行すると、画面が真っ白になり、ブラウザの手動リロードが必要になる。

❌ 問題のあるコード

JavaScript
google.script.run
  .withSuccessHandler(msg => {
    showMessage(msg + ' 処理が完了しました!');
    setTimeout(() => {
      location.reload(); // ← これで画面が真っ白になる
    }, 2000);
  })
  .serverFunction();

環境

HTMLファイルのサイズ: 約6,000行、200KB以上
データ量: Base64エンコードされた大量のJSONデータをテンプレートに埋め込み
ブラウザ: Chrome(最新版)

🔍 試したこと

1. setTimeoutで遅延させる ▶️ ❌ 効果なし

```js
setTimeout(() => {
  location.reload();
}, 3000); // 3秒待っても真っ白
```
→ 結果: 変わらず真っ白になる

2. 二段階setTimeoutで十分な時間を確保 ➡️ ❌ 効果なし

```js
setTimeout(() => {
  setTimeout(() => {
    location.reload();
  }, 500);
}, 100);
```
→ 結果: これでも真っ白になる

💡 原因の分析

私の問題のGASのWebアプリでは、location.reload()によるページ再読み込み時に以下の処理が発生しました。

  • HTMLの再パース(6,000行分)
  • JavaScriptの再解釈
  • GASテンプレートエンジンによるデータ注入(Base64データのデコード等)
  • DOMの再構築

これらの処理が完了する前にブラウザが描画を試みると、画面が真っ白になるという現象が発生します。

特にGASでは、
doGet()
でテンプレートにデータを渡す際の初期化プロセスに時間がかかるため、通常のWebアプリケーションよりも問題が顕著に現れます。

✅ 解決策: Promise/async-awaitでデータ再取得

location.reload()を使わず、必要なデータのみをサーバーから再取得して画面を更新する方法に変更しました。

アプローチ

  • google.script.runをPromiseでラップする
  • async/awaitで可読性の高いコードにする
  • サーバーから最新データを取得して、必要な部分のみを再描画

📝 実装方法

Step 1: Promiseヘルパー関数の作成

まず、google.script.runをPromiseでラップする汎用ヘルパー関数を作成します。

/**
 * google.script.runをPromiseでラップするヘルパー関数
 * @param {string} functionName - 呼び出すサーバー関数名
 * @param {...any} args - サーバー関数に渡す引数
 * @returns {Promise} - サーバーからの結果を返すPromise
 */
function callServerAsync(functionName, ...args) {
  return new Promise((resolve, reject) => {
    google.script.run
      .withSuccessHandler(resolve)
      .withFailureHandler(reject)
      [functionName](...args);
  });
}

Step 2: データ再取得関数の作成

サーバーから最新データを取得して画面を更新する関数を作成します。

/**
 * サーバーから最新データを取得して画面を更新
 * location.reload()の代わりに使用
 */
async function refreshPageData() {
  try {
    setLoading(true);
    
    // サーバーから最新データを取得
    const newData = await callServerAsync('getLatestData');
    
    // グローバル変数を更新
    window.appData = newData;
    
    // 画面を再描画
    renderPage(newData);
    
    setLoading(false);
    
  } catch (error) {
    setLoading(false);
    console.error('データ取得エラー:', error);
    showErrorMessage('データの更新に失敗しました');
    throw error;
  }
}

Step 3: 既存コードの書き換え

従来のgoogle.script.runのコールバック形式から、async/awaitに書き換えます。

❌ 変更前

function executeProcess() {
  if (!confirm('処理を実行しますか?')) return;
  
  setLoading(true);
  showMessage('処理中...', 'loading');
  
  google.script.run
    .withSuccessHandler(msg => {
      setLoading(false);
      showMessage(msg, 'success');
      setTimeout(() => {
        location.reload(); // ← 問題の箇所
      }, 2000);
    })
    .withFailureHandler(error => {
      setLoading(false);
      showMessage('エラー: ' + error.message, 'error');
    })
    .processData();
}

✅ 変更後

async function executeProcess() {
  if (!confirm('処理を実行しますか?')) return;
  
  try {
    setLoading(true);
    showMessage('処理中...', 'loading');
    
    // サーバー関数を呼び出し(async/await)
    const msg = await callServerAsync('processData');
    
    // 成功メッセージを表示
    showMessage(msg + ' 完了しました!', 'success', 5000);
    
    // 画面データを更新(リロードなし)
    await refreshPageData();
    
  } catch (error) {
    setLoading(false);
    showMessage('エラー: ' + error.message, 'error');
  }
}

サーバー側(GAS)のコード例

/**
 * クライアントに返すデータを取得
 */
function getLatestData() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('データ');
  const data = sheet.getDataRange().getValues();
  
  // データを加工して返す
  return {
    records: data.slice(1), // ヘッダーを除く
    lastUpdated: new Date().toISOString(),
    summary: calculateSummary(data)
  };
}
/**
 * データ処理を実行
 */
function processData() {
  // 何らかの処理を実行
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('データ');
  // ... 処理内容 ...
  
  return '処理が正常に完了しました';
}

🎯 メリット

この解決策により、以下のメリットが得られました:

1. 信頼性の向上

✅ location.reload()による不確実な初期化を排除
✅ GASの再初期化エラーが発生しない
✅ 画面が真っ白になる問題を完全に解決

2. パフォーマンス向上

✅ ページ全体の再読み込みが不要
✅ 必要なデータのみをサーバーから取得(高速)
✅ 大きなHTMLファイルの再パースが不要

3. ユーザー体験の改善

✅ 画面のちらつきがなくなる
✅ ローディング表示がスムーズ
✅ 処理完了メッセージをゆっくり確認できる

4. コードの可読性とメンテナンス性

✅ async/awaitで非同期処理の流れが明確
✅ コールバック地獄を回避
✅ エラーハンドリングがtry-catchで統一
✅ callServerAsyncヘルパーは他の機能でも再利用可能

📊 比較表

項目 location.reload() データ再取得
画面の安定性 ❌ 真っ白になることがある ✅ 安定
処理速度 ❌ 遅い(全体再読み込み) ✅ 高速(差分のみ)
ネットワーク負荷 ❌ 大きい ✅ 小さい
コードの可読性 △ コールバック形式 ✅ async/await
カスタマイズ性 ❌ 低い ✅ 高い(更新箇所を選択可能)

💭 注意点

タイミングの考慮

データ再取得のタイミングは、ユーザーにメッセージを見せる時間も考慮して設計すると良いでしょう。

// 成功メッセージを表示
showMessage(msg + ' 完了しました!', 'success', 5000);

// メッセージ表示中に裏でデータ更新
await refreshPageData();

グローバル状態の管理

refreshPageData()では、ページ初期化時と同じ状態変数の更新が必要です。DOMContentLoadedのロジックを関数化して再利用するのがオススメです。

// 初期化ロジックを関数化
function initializeAppState(data) {
  window.appData = data;
  window.currentView = 'dashboard';
  // ... その他の状態変数
}
// DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
  const initialData = decodeAndParseData();
  initializeAppState(initialData);
  renderPage(initialData);
});
// データ再取得時も同じロジックを使用
async function refreshPageData() {
  const newData = await callServerAsync('getLatestData');
  initializeAppState(newData); // 同じ関数を再利用
  renderPage(newData);
}

📝 まとめ

GASのWebアプリでlocation.reload()が原因で画面が真っ白になる問題は、以下の方法で解決できました:

  • google.script.runをPromiseでラップする汎用ヘルパーを作成
  • async/awaitで非同期処理を簡潔に記述
  • location.reload()を廃止し、サーバーから最新データのみを再取得
  • 必要な部分のみを再描画することで高速化

この方法により、信頼性・パフォーマンス・コードの可読性すべてが向上しました。

GASで大規模なWebアプリを開発している方は、ぜひ試してみてください!

0
0
0

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
0
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?