Express で簡単な JWT の API を作って実験していた際に middleware を async function に変更したら UnhandledPromiseRejectionWarning
が出るようになってハマってしまったのでメモ
UnhandledPromiseRejectionWarning になったコード
// router const router = require('express').Router(); const verify = require('./verify'); router.get('/', verify, (req, res) => { res.send(req.data); }); module.exports = router;
middleware
// verify.js const jwt = require('jsonwebtoken'); const { checkTokenValidity } = require('./token'); module.exports = async function(req, res, next) { const token = req.header(TOKEN_HEADER); if (!token) { return res.status(401).send('Access Denied'); } try { // 有効期限切れなどで verify ができないと catch に処理が流れる const verified = jwt.verify(token, TOKEN_SECRET); // token を redis に問い合わせて revoke されてないかチェックする const isValidity = await checkTokenValidity(token); if (!isValidity) { throw new Error('Token is revoked'); } req.data = verified; next(); } catch(err) { res.status(400).send('Invalid Token'); } }
token が渡されない場合や、token が有効な時は問題がないのですが、token が有効でなく Invalid Token
になる場合に UnhandledPromiseRejectionWarning
が発生します。
note. UnhandledPromiseRejectionWarning
UnhandledPromiseRejectionWarning は非同期処理の Promise でエラーが catch できなかった時に発生するエラー
非同期関数の await checkTokenValidity(token)
を追加した後から、この warning が発生するようになったので、この関数が悪いのかと思い別途 try 〜 catch
で囲ってみたり、コメントアウトしてみたり、Promise.reject()
を書いてみたりしても変化がなく、どうやら middleware を async function
にした事で warning が発生するようになっていました。
HEADER が 2重に返されていたのが原因だった
長い warning をよく読んでみると ERR_HTTP_HEADERS_SENT という記述があり、どうやら res.send()
された後に更に res.send()
がされているのがこの warning 発生の直接的な原因となっているようでした。
async function 内では明示的に return する必要がある
middleware が async function でない場合は next()
が呼ばれず res.send()
してしまえば、その場で処理が終了になっていたものが、async function にしたことで返される値を待つようになってしまったので、res.send()
で処理が終了にならず、関数の最後に返される undefined
を Promise.resolve()
として受け取っていたということでした。
// verify.js module.exports = async function(req, res, next) { // ... try { // … } catch(err) { res.status(400).send('Invalid Token'); } // ここで即時 undefined が return される } // router router.get('/', verify, (req, res) => { // verify が async function なので、return を待つのでエラーの場合 // 関数の最後で返される undefined を Promise.resolve として次の res.send() が呼ばれる res.send(req.data); });
async function な middleware で res.send
する時は明示的に return
をする
明示的に return res.send()
をすればOK
// verify.js module.exports = async function(req, res, next) { // ... try { // … } catch(err) { // エラーの場合はこれが返されて関数は終了 return res.status(400).send('Invalid Token'); } // ここに到達することはない } // router router.get('/', verify, (req, res) => { // エラーの場合先に res.send() が返されて実行されているので、res.send(req.data) は実行されない res.send(req.data); });
単純に async / await の挙動の考慮漏れが原因でした…
小一時間ほどハマってしまった。
[参考]
- node.js — ERR_HTTP_HEADERS_SENT:ヘッダーがクライアントに送信された後、ヘッダーを設定できません
- Node.js で UnhandledPromiseRejectionWarning がでたとき - tasuwo's notes
- async/await の例外処理まとめ - Qiita
- アーティスト:V.A.
- 発売日: 2018/06/27
- メディア: CD
プリパラの履修始めました。