3
0

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.

nextjsのapi routeをローカルでは問題が出ないのにvercelに置いた途端挙動不明になる場合

Last updated at Posted at 2021-03-12

症状

こういう感じの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側に返すために、自身の処理を見直して

  1. どこでPromiseとして返すべきか
  2. どこでasync/awaitを使えるのか
  3. 処理内で複数の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()
  })
}
3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?