11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

フロントでfirestoreのエラーを検知したい

Posted at

とあるプロジェクトにて。
こんなかんじでfirestoreのエラー検知をしようとしているコードがありました。

try {
  connectFirestore();
} catch {
   window.alert('firebase接続失敗!');
}

try {
  watchFirestoreUpdate();
} catch {
   window.alert('firebase参照失敗!');
}

try {
  updateFirestore();
} catch {
   window.alert('firebase更新失敗!');
}

しかし、意図的にエラーを起こして動作確認したところ、、、
検知できてませんでした。


この記事は、firestoreのエラー検知をちゃんとやろうとした、格闘の記録です。 firestoreは使い慣れていないため、変なことをしていたらご指摘いただけるとありがたいです。

## 前提 * 言語はtsです * フロントエンドです * firebaseのバージョンは7です * firestoreをつかって、別システムと連携をするページです * 監視や更新がひっそりと失敗するのは、要件上クリティカルにまずいです
## アウトライン やろうとしたことの一覧と、結果を記載します。
やろうとしたこと 結果 何をした?
初回接続エラーの検知 できた setTimeout()で接続タイムアウトを検知
途中切断の検知 一応できたけど挙動が怪しいので導入見送り setInterval()で定期的に接続状態をチェック
NW切断の検知 できた window.addEventListener('offline')を設定
参照エラーの検知 できた コールバックを使う
更新エラーの検知 できた コールバックを使う

## エラー検知、その前に 以降のコードでは、↓を使います。
import firebase from 'firebase';

// 本記事の各所で使う汎用モジュール
let singletonApp: firebase.app.App | null = null;
export const getApp = (): firebase.app.App => {
  if (singletonApp === null) {
    // 初回だけinitialize (2回やるとduplicateエラーになる)
    singletonApp = firebase.initializeApp(/*引数省略*/);
  }
  return singletonApp;
};
export const getRef = (path?: string): firebase.database.Reference => {
  return getApp().database().ref(path);
};

## 初回接続エラーの検知 ページ読み込みの直後に行っている、認証や、firebase.database.Reference取得の失敗を検知できるようにしました。 認証エラーは素直にPromiseで検知できたのですが、reference取得エラーの検知が厄介でした。 接続失敗したときに各種イベントが発火するわけもないため、タイムアウトで検知するようにしました。
// ロード時に実行する関数
//  => 認証エラーと接続タイムアウトでalertを出す
export const onLoad = (): void => {
  getApp()
      .auth()
      .signInWithEmailAndPassword('e-mail@gmail.com', 'password1234')
      .then(() => {
        console.log('firebase: authentication ok');
      })
      .catch((err) => {
        console.log(err);
        window.alert('firebase認証失敗')
      });
  alertOnConnectionTimeout();
};

const alertOnConnectionTimeout = (): void => {
  // TIMEOUT_SEC秒経ってもfirebase接続ができなければalertを出す
  const TIMEOUT_SEC = 15;
  checkConnectionTimeout(TIMEOUT_SEC)
      .then(() => {
        console.log('firebase: connection ok');
      })
      .catch((err) => {
        console.log(err);
        window.alert('firebase接続失敗');
      });
};

const checkConnectionTimeout = (timeoutSec: number): Promise<void> => {
  return new Promise((resolve, reject) => {
    getRef('.info/connected').on('value', (snapshot: any) => {
      // 接続したらここに来る
      if (snapshot.val() == false) {
        return;
      }
      resolve();
    });
    window.setTimeout(() => {
      // タイムアウトしたらここに来る
      reject(new Error('timeout'));
    }, timeoutSec * 1000);
  });
};

なお、以下はダメでした。
// ダメだったケース
export const onLoad = (): void => {
  checkConnectionOnce();
};
const checkConnectionOnce = (): void => {
  getRef('.info/connected').once('value', (snapshot: any) => {
    // 接続失敗なら、そもそもここに来ない
    if (snapshot.val() == false) {
      // 接続後、1回目のvalueイベントでここにくる (=正常系でアラートが出てしまう)
      window.alert('Firebaseの接続に失敗しました');
      return;
    }
    // 接続後、2回目のvalueイベントでここにくる (=onceだとここに到達しない)
    // => よって、↑ではonceではなくonで見ることにした
    console.log('firebase: connection ok');
  });
};

## 途中切断の検知 自分 <-(a)-> firebase <-(b)-> 他人 のうち、(a)の途中切断を検知しようとしました。 一応、以下のコードで検知できるとは思うのですが、
  • devtoolで確認したところメモリリークしているっぽい挙動だった
  • 次項のNW切断エラーを入れれば大半のケースで検知できるし、シンプルになる

という理由から、導入はやめました。
(NW切断の検知だけだと、NW正常でfirebase側に障害があった場合は検知できません。とはいえ参照・更新のタイミングでエラー検知できるため、大過ないだろうと考えました)

// ロード時に実行する関数
export const onLoad = (): void => {
  // INTERVAL_SEC秒ごとに、firebaseの接続状態をチェック
  const INTERVAL_SEC = 30;
  setInterval(
    checkConnectionTimeout, // 中身は前項を参照
    INTERVAL_SEC * 1000
  );
};

なお、onDisconnectといういかにも今回のケースに使えそうな名前の関数がありましたが、これは使えませんでした。

// onDisconnect()を使うと、
// 切断時に、firebase側でfirestoreの値を更新してくれる。
// ただ、値を更新をするためのインターフェースしか用意されていないため、
// window.alert()を出す、みたいなことには使えない。
getRef('my/app/path/is_connected').onDisconnect().set(false);

/* 余談
 * onDisconnect()による値の更新には罠があります。
 * タブを閉じた場合は、即時値が反映されますが、
 * NW切断の場合は、3分程たたないと値が反映されません。
 * NW切断も含めてリアルタイムに値を反映したい場合は、何かしら別の手段が必要です。
 */

## NW切断の検知 firebaseではなく、ブラウザの機能をつかいました。超簡単。
window.addEventListener('offline', () => {
  window.alert('ネットワーク切断');
}, false);

## 参照エラーの検知 [Reference](https://firebase.google.com/docs/reference/js/firebase.database.Reference)を使う場合、promiseではなく、なつかしのコールバックを使ってエラーを検知します。この仕様に気づかずそこそこハマりました。 (改めて型定義を見返すまで気づけなかったです。。。盲点でした。)
const watchMyAppPath = () => {
  getRef('my/app/path').on('value', (snapshot) => {
    console.dir(snapshot.val());
  }, captureWatchError);
};

const captureWatchError = (err: any) => {
  if (err) {
    console.log(err);
    window.alert('firebase参照失敗');
  }
};

なお、試してませんが、以下を使えば普通にtry/catchできるかもしれません。

  • promisifyを使ってpromise化する
  • ThenableReferenceを使ってみる
    (名前から想像するに、thenやcatchを使えるようにしたReferenceと思われます。ドキュメントをみても使い方が分からなかったのと、変更の影響がでかそうだったため導入を見送りました)

## 更新エラーの検知 参照エラーと同じく、コールバックを使いました。
const updateMyAppPath = () => {
  getRef('my/app/path/boolean').set(true, captureWatchError);
};

const captureUpdateError = (err: any) => {
  if (err) {
    console.log(err);
    window.alert('firebase更新失敗');
  }
};

## まとめ * 異常系もちゃんと動作確認をしよう * .info/connectedを見ればfirebaseの接続状態が分かる * window.addEventListener('offline') でNW切断を検知できる * onDisconnect()でfirebase切断時に値を更新できるが、NW切断だとラグがあるので注意 * firestoreの参照、更新エラーにはコールバック関数を使う
以上です。
11
9
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
11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?