Express服务调用res.json崩溃及Heroku部署异常排查
问题根因
代码存在3个核心问题,直接导致本地运行崩溃、Heroku部署后服务被强制终止:
- 异步逻辑处理错误:
Array.prototype.forEach不会等待内部异步操作执行完成,遍历还未结束、数据库写入操作仍在排队时就调用res.json返回响应,后续异步任务抛出的未捕获异常会直接终止Node进程 - 接口异常未捕获:
/:slug路由的OpenseaScraper.basicInfo调用没有包裹异常处理,OpenSea接口返回404时,依赖内置axios抛出的错误没有被拦截,直接触发进程崩溃,日志里请求favicon.ico返回404就是该逻辑触发的连锁问题 - 部署配置错误:Heroku环境如果使用nodemon作为生产启动命令,会因为生产环境无文件变更触发监听异常,直接导致进程崩溃退出,和日志里
app crashed - waiting for file changes before starting的记录完全对应
你观察到的「传参给res.json就崩溃、移除res.json本地正常Heroku报错」现象本质是:调用res.json后请求生命周期正常结束,后续未捕获的异步错误会直接触发进程退出;移除res.json后请求一直处于挂起状态,本地不会触发进程终止逻辑,但Heroku有严格的请求超时机制,挂起请求超过超时阈值会直接强制杀进程,返回H13、H21类错误码。
修复方案
1. 修正根路由异步逻辑
不要用forEach处理异步遍历,改用for...of循环等待所有数据库操作完成后再返回响应,同时补全数据库操作的异常捕获,避免单条记录写入失败打崩整个接口。
替换原根路由代码为:
server.get('/', async (req, res) => { try { const NFTs = [] const ranking = await OpenseaScraper.rankings() // 用for...of替代forEach,正确等待异步操作执行 for (const item of ranking) { const slug = item.slug const name = item.name const logoUrl = item.logo NFTs.push({slug, name, logoUrl}) // 单条数据库操作加异常捕获 try { const existNFT = await NFT.findOne({ "slug": slug }) if (!existNFT) { const newNFT = new NFT({slug, name, logoUrl}) await newNFT.save() } } catch(dbErr) { console.error('NFT记录写入失败:', dbErr) } } // 所有逻辑执行完成后再返回响应 res.json({ list: NFTs }) } catch(err) { console.error('排名接口报错:', err) // 异常场景必须返回响应,避免请求挂起 res.status(500).json({ msg: '获取NFT排名失败' }) } })
2. 补全slug路由的异常捕获
原slug路由没有任何异常处理逻辑,OpenSea接口404时会直接抛错打崩进程,替换为以下代码:
server.get('/:slug', async (req, res) => { try { const basicInfo = await OpenseaScraper.basicInfo(req.params.slug) const floorPrice = basicInfo.floorPrice // 先执行数据库更新,再返回响应 await NFT.findOneAndUpdate( { "slug": req.params.slug }, {$set: {"floorPrice": floorPrice}} ) res.json({ name: req.params.slug.replaceAll('-', ' '), floorPrice: floorPrice }) } catch(err) { console.error('NFT详情查询失败:', err) res.status(500).json({ msg: '获取NFT详情失败' }) } })
3. 修正数据库连接逻辑
原mongoose连接写法没有捕获连接异常,且存在拼写错误,替换为以下写法:
mongoose.connect(process.env.MONGODB_URL) .then(() => console.log('This application has been connected to the Mongo DB!')) .catch(err => console.error('数据库连接失败:', err))
代码中定义的无用常量const PORT = 8001可以直接删除,现有监听逻辑已经正确优先读取Heroku分配的process.env.PORT变量,不存在端口适配问题。
4. 调整Heroku部署配置
- 生产环境禁止用nodemon启动服务,修改package.json的脚本配置,区分开发和生产启动命令:
{ "scripts": { "start": "node 你的入口文件名.js", "dev": "nodemon 你的入口文件名.js" } } - 确认Heroku后台配置的环境变量中已经正确填写
MONGODB_URL,避免数据库连接失败导致进程退出。
内容的提问来源于stack exchange,提问作者Daniel Brannon
相关产品推荐
相关产品推荐

