最近気づいたasync/awaitの使いどころ

最近気づいたこと

これを気づいたきっかけについてメモしておく。

FirebaseのCloudFunction

最近この記事を参考に認証コードログインをFireabaseのCloudFunctionで実装しようとしているのだが
Firebase Authenticationで認証コードログインを実現する - Qiita

この中で、CloudFunction(サーバ側)のJSプログラムでCall functionというものを使っている。
Call functions from your app  |  Firebase Documentation

このCall functionでデータを返すには、Promiseで返さなくてはならない。つまり、Promiseを返すような関数を書かなくてはならない。

さらに今回は認証コードログインのために、この関数の中でもcustome claimをユーザーに付与するsetCustomUserClaimsを待たなくてはならない。

つまり中でPromiseを待って、外にPromiseを返す関数を書かなくてはならない。このようなときにasync/awaitはぴったりだと気付いた。

Promiseを使った場合

const VERIFY_CODE = "0112"

exports.authWithCode = functions.region('asia-northeast1').https.onCall( (data, context) => {
  // 設定された認証コードと送られてきた認証コードが一致するか判定
  if(data.verifyCode === VERIFY_CODE) {
    return admin.auth().setCustomUserClaims(data.uid, {codeVerified: true})
      .then(
        ()=>{
          functions.logger.log("Success: ",data.uid," is authenticated !")
          return { status: 'ok', message: '認証しました。' }
        },
        ()=>{
          functions.logger.warn("Failed: ",data.uid," is failed to login ...")
          return { status: 'error', message: 'ログイン処理に失敗しました。' }
        }
      )
  }else{
    functions.logger.warn("Failed: ",data.verifyCode," is invalid code ...")
    return new Promise(
      (resolve)=>resolve({ status: 'error', message: '認証コードが正しくありません。' })
    )
  }

})

functions.https.onCallのコールバック関数の戻り値はPromiseでなければならない。 そして分かりにくい点が、長々と続いているPromiseオブジェクト.then(()=>{...})自体が返すべきPromiseオブジェクトなことである。

この最初のPromiseオブジェクトの結果に応じて結果(今回の場合成功すれば、{ status: 'ok', message: '認証しました。' }、失敗すれば{ status: 'error', message: 'ログイン処理に失敗しました。' }という内容)をresolveするPromiseを返すためには、最初のPromiseオブジェクトの前にreturnを書かなければならない。これはPromiseの仕組みを知っていれば当たり前の話なのだが、これでは処理の流れとコードを書くor読む順番が逆でなんともわかりにくい。

加えて、下の認証できなかった場合のコードではPromiseオブジェクトを生成してresolveを登録して、それを返している。これもめんどくさい処理である。

async/awaitであればこれらの処理がよりシンプルに書ける。

async/awaitを使った場合

const VERIFY_CODE = "0112"

exports.authWithCode = functions.region('asia-northeast1').https.onCall( async (data, context) => {
  // 設定された認証コードと送られてきた認証コードが一致するか判定
  if(data.verifyCode === VERIFY_CODE) {
    try{
      await admin.auth().setCustomUserClaims(data.uid, {codeVerified: true})
      functions.logger.log("Success: ",data.uid," is authenticated !")
      return { status: 'ok', message: '認証しました。' }
    }catch{
      functions.logger.warn("Failed: ",data.uid," is failed to login ...")
      return { status: 'error', message: 'ログイン処理に失敗しました。' }
    }
  }else{
    functions.logger.warn("Failed: ",data.verifyCode," is invalid code ...")
    return { status: 'error', message: '認証コードが正しくありません。' }
  }

})

コールバック関数はPromiseを返す必要があるのだが、これはasync関数にしてしまえば単純にreturnを書いただけで、それをresolveするPromiseが返されるので今回の用途に合っている。

また、関数の中でPromiseを待つ処理があるが、これはawaitで待って、次の処理を書けばいい。これもこちらの方がすっきりする。

そして無理やりにPromiseオブジェクトを作る必要もなくなった。

実際、この関数の行数も22行→16行とすっきりした。

promiseとasync/awaitの使い分けについて

いつでもasync/awaitを使った方がいいというわけではなく、どちらも使い分けだと考えられる。今回のような場合はasync/awaitの威力が発揮できたが、また別の場合は単純なPromiseのほうがいい場合もあると思われる。