症状
こういう感じのPromise絡みの処理があり、最後にstatus:200を返してる。
これはローカルだと意外とすんなり動く。が、これをvercelすると途端に動いたり動かなかったり、エラーになったり、わけがわからない状態が続く。
import admin from "./my-firebase-admin"
import type { NextApiRequest, NextApiResponse } from "next"
class VeryComplicatedProcesses{
process(){
admin.firestore().collection("...").get().then((snapshot) => {
if(snapshot.empty){
// 処理 ...
// ローカルだとこれは意外と動く
}
})
}
}
export default (req: NextApiRequest, res: NextApiResponse) => {
const vcp = new VeryComplicatedProcesses()
vcp.process()
res.status(200).end()
}
問題
良くは調べてないが、多分ローカルだとサーバーは常駐しているので処理が途中で切られることはないが、vercel上だと常時サーバーを起動させている様子ではなく、必要最低限でサーバーが起動・停止を繰り返しているよう。
なので、Promiseを待たずにstatus:200を送ってると、その時点でサーバーが破棄される?とかで非同期処理が終わらないままになってしまう。
解決方法1
最初async/awaitをこういう感じ付けてやってみたが、意外とこれもコケたりコケなかったり挙動不審になる。使ってる非同期処理がfirestoreだったので初期起動とかに時間がかかる場合、どこかでタイムアウトになっている様子。
export default async (req: NextApiRequest, res: NextApiResponse) => {
const vcp = new VeryComplicatedProcesses()
await vcp.process()
res.status(200).end()
}
解決方法2 - うまく行ってる
API route自体にPromiseをreturnで送りつけてやる(参考)。こうすることでサーバー側はPromiseの処理終わりで送られるstatus:200を受け取り、処理を終了することになる。
Promiseを最終的にroute側に返すために、自身の処理を見直して
- どこでPromiseとして返すべきか
- どこでasync/awaitを使えるのか
- 処理内で複数のPromiseがある場合、Promise.allなどを使う必要があるか?
などをよく検討しないといけない。
class VeryComplicatedProcesses{
process(): Promise<void>{
return admin.firestore().collection("...").get().then((snapshot) => {
if(snapshot.empty){
// 処理 ...
// ローカルだとこれは意外と動く
}
})
}
}
export default (req: NextApiRequest, res: NextApiResponse) => {
const vcp = new VeryComplicatedProcesses()
// promiseをそのまま渡してやる
return vcp.process().then(() => {
// 処理が全て終わった時点でstatus:200
res.status(200).end()
})
}
デバック方法
ローカルで次のようにやってみると意外と視覚的にわかりやすかった。
最後に来るべきのconsole.log('res.status(200).end():')
より後にログが出るのはおかしい。
import admin from "./my-firebase-admin"
import type { NextApiRequest, NextApiResponse } from "next"
class VeryComplicatedProcesses{
async process(): Promise<void>{
const promises = [
this.processA(),
this.processB()
]
// この中にPromiseが入っていることを確認
// Typescriptなら戻り値の型宣言でチェックできるが、JSだとreturn漏れが起きやすい
console.log('promises:', promises)
await Promise.all(promises)
console.log('Promise.all:')
}
processA(): Promise<YourGeneric> {
// 何らかの非同期処理 promiseをそのまま返す
return PromiseReturning.call()
}
processB() {
// 何らかの非同期処理 promiseをそのまま返す
return PromiseReturning.anotherCall(async (object) => {
// この匿名関数の中だけでasync/awaitで同期処理
await object.some()
object.property
console.log('object.property:', object.property)
})
}
}
export default (req: NextApiRequest, res: NextApiResponse) => {
const vcp = new VeryComplicatedProcesses()
return vcp.process().then(() => {
console.log('res.status(200).end():')
res.status(200).end()
})
}