Firebaseとfunctionsでエラー「Error: Could not load the default credentials. Browse to」

# 結論
Firebase Cloud Functionsは一度終了してしまうと、コールバックを経由したプロセッサへのアクセスを許可してくれません。そのため、エラーが発生します。`Could not load the default credentials error.`

`async/await`や`Promise`でちゃんと非同期処理のコールバックを監視しましょう！

# 課題

## エラー内容
### firebaseとcloud functionsでこんなエラーが出て関数がコケる

さて、今回はfirebaseとcloud functionsのadmin sdkを使って
非同期処理を必要とする処理を書いていた所出会ったエラーについて記事にします。

```
Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
```

## ググって見ると秘密鍵を作って初期化しろと…

下記の記事を読んだりすると
https://excode.jp/blog/cloud-functions-error-could-not-load-the-default-credentials/

1. 秘密鍵を作れ
2. 秘密鍵をfunctionsのファイルの中にjsonで置け
3. index.jsまたはtsで呼び出して`admin.initializeApp()`の引数に渡せ

## 何か違くない？？
私はここで疑問に思いました。

### パラメーター無しの初期化はOKのハズ
下記の公式ドキュメントを読むと`admin.initializeApp()`はsdkを介してデプロイすると
`FIREBASE_CONFIG`環境変数からオプションを読み取ってデフォルトアップを初期化してくれるはずです。

https://firebase.google.com/docs/admin/setup?authuser=1

### 秘密鍵を置くと環境が違うとソースコードを使い回せない

これは別に方法はあるのかも知れませんが、もしも本番と開発などに環境を分けている場合
それぞれの環境毎に秘密鍵を用意してソースコードに置いて上げて環境を判断して読み込むファイルを変えてあげなければならなくなる。

ちょっとそれは面倒だ。

# 解決

## 非同期処理のコールバックを監視する

下記の記事にも色んな事が書かれていますが私が参考にしたのはここ

https://stackoverflow.com/questions/58127896/error-could-not-load-the-default-credentials-firebase-function-to-firestore

>I ran into the same problem myself. Sometimes the function works and many times it would through the Error: Could not load the default credentials error. The problem I believe have been solved by watching for the Callbacks. You have to keep the function running until the callbacks have been called using the await and async prefixes.

>Firebase Cloud Functions don't allow the access to the processor through callbacks once it's been terminated! That's why we get the Error: Could not load the default credentials error.

>So, whenever you have a .then() function prefix it with await and prefix the function it's inside it with async and prefix any call to the function with await.

```
async function registerUser(..) {
    ...
    await admin.firestore().collection(..)...
    ...
}
```

### cloud functionsはコールバックが終了してしまうと認証が無くなる

あまり詳しく調べて居ないのですが、すべての処理を実行し切る前に非同期処理でコールバックが返って終了してしまうと、cloud functionsは認証情報を失ってしまい認証エラーを起こしてしまいます。

そこで、私の場合は関数の元で`async/await`を書きました。

エラーを起こしている時のソースコードでは下記の様に途中に `Promise` がありました。

```ts
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
const tokyoRegion = 'asia-northeast1'
const firestore = admin.firestore()
const refUsers = collection('users')
const refHoge = collection('hoge')

const deleteData1 = (documentId: string) => {
 cosnt query = refUsers
   .where('data_doc_id', '==', documentId)
   .limit(500)

  const isDelete = new Promise((resolve, reject) => {
    // 割愛！！
    deleteBatchFunction(query, resolve, reject)
  })
}

const deleteData2 = (documentId: string) => {
 cosnt query = refHoge
   .where('data_doc_id', '==', documentId)
   .limit(500)

  const isDelete = new Promise((resolve, reject) => {
    // 割愛！！
    deleteBatchFunction(query, resolve, reject)
  })
}

module.exports = functions
  .region(tokyoRegion)
  .firestore.document('data/{documentId}')
  .onDelete((snap, context) => {
    try {
      const { documentId } = context.params

      deleteData1(documentId)
        .catch((err) => { throw new Error(err) })

      deleteData2(documentId)
        .catch((err) => { throw new Error(err) })
    } catch (err) {
      throw new Error(err)
    }
  })

```

### 根元の関数でasync/await

```ts
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
const tokyoRegion = 'asia-northeast1'
const firestore = admin.firestore()
const refUsers = collection('users')
const refHoge = collection('hoge')

const deleteData1 = (documentId: string) => {
 cosnt query = refUsers
   .where('data_doc_id', '==', documentId)
   .limit(500)

  const isDelete = new Promise((resolve, reject) => {
    // 割愛！！
    deleteBatchFunction(query, resolve, reject)
  })
}

const deleteData2 = (documentId: string) => {
 cosnt query = refHoge
   .where('data_doc_id', '==', documentId)
   .limit(500)

  const isDelete = new Promise((resolve, reject) => {
    // 割愛！！
    deleteBatchFunction(query, resolve, reject)
  })
}

module.exports = functions
  .region(tokyoRegion)
  .firestore.document('data/{documentId}')
  // ------ 無名関数にasyncを追加 ------
  .onDelete(async (snap, context) => {
    try {
      const { documentId } = context.params

      // ------ awaitを追加 ------
      await deleteData1(documentId)
        .catch((err) => { throw new Error(err) })

      // ------ awaitを追加 ------
      await deleteData2(documentId)
        .catch((err) => { throw new Error(err) })
    } catch (err) {
      throw new Error(err)
    }
  })

```