最近気づいたasync/awaitの使いどころ
最近気づいたこと
「わざわざasync関数作るのめんどくさいし、ネストが浅いなら、async/await使うより素直にthen書いた方がよくない?」とか思ってたんだけどネスト浅くてもAPIの要請で自分でPromise返すような関数書かなきゃならないときとか断然asyncで書いた方が書きやすいな・・・
— SolKul (@ICR3_5) November 19, 2021
これを気づいたきっかけについてメモしておく。
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のほうがいい場合もあると思われる。