使用Express JS时遇NodeJS错误:ERR_HTTP_HEADERS_SENT问题求助
Express API 报错:ERR_HTTP_HEADERS_SENT 问题求助
问题描述
我是API开发入门学习者,正在跟随YouTube开发者Ania Kubow的视频教程学习,使用ExpressJS、Cheerio与Axios三个JavaScript库开发API。目前代码运行出现错误,完成Cheerio each循环一次后程序崩溃,错误信息为Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client。我已尝试更换视频中的Node版本、重构代码、搜索解决方案,但均未解决问题,特此寻求技术帮助。
核心代码
app.get('/news', (req, res) => { axios.get('https://www.theguardian.com/environment/climate-crisis') .then((response) => { // Store the html retrieved via the axios http GET request const html = response.data; // Load the retrieved html into the cheerio instance and store to $ cheerio selector const $ = cheerio.load(html); // DEBUG purpose stuff var loopCounter = 0; var climate_A_Elements = $('a:contains(climate)', html).length; console.log('Number of articles found: ' + climate_A_Elements); // // Get all articles that contain 'climate' in the <a /> element var allFoundArticles = $('a:contains("climate")', html); // Iterate through all found articles using cheerio forEach loop allFoundArticles.each(function () { // Assign article title and url it is located at to variables var title = $(this).text(); var url = $(this).attr('href'); // Push the previously defined vars to the previously defined array articlesData.push({ title, url, }); // Output the article details to page as response res.json(articlesData); // Add to loop count for debugging purposes and log in console loopCounter += 1; console.log('Loops: ' + loopCounter); }).catch(err => { console.log(err); }) }) })
错误输出
node:internal/errors:484 ErrorCaptureStackTrace(err); ^ Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at new NodeError (node:internal/errors:393:5) at ServerResponse.setHeader (node:_http_outgoing:644:11) at ServerResponse.header (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\node_modules\express\lib\response.js:794:10) at ServerResponse.json (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\node_modules\express\lib\response.js:275:10) at Element.<anonymous> (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\index.js:65:21) at LoadedCheerio.each (C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\node_modules\cheerio\lib\api\traversing.js:519:26) at C:\Users\etomm\Downloads\Daniel\Dev\Web\climate-change-api\index.js:52:30) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { code: 'ERR_HTTP_HEADERS_SENT' } Node.js v18.12.1 [nodemon] app crashed - waiting for file changes before starting...
问题原因与修复方案
核心原因
你在Cheerio的each循环内部调用了res.json(articlesData),这会导致每遍历一个文章就给客户端发送一次HTTP响应。但HTTP协议规定一个请求只能对应一个响应,第一次响应发送后,响应头已经传递给客户端,第二次再调用res.json就会触发ERR_HTTP_HEADERS_SENT错误。
另外还有几个小问题:
articlesData如果是全局变量,多次请求会导致数据累加- Cheerio选择器里的
"climate"是多余的转义,且不需要在选择器里传入html参数(因为$已经加载了页面内容) catch的位置错误,无法捕获Axios请求的错误
修复后的代码
app.get('/news', (req, res) => { // 每次请求初始化空数组,避免全局数据污染 const articlesData = []; axios.get('https://www.theguardian.com/environment/climate-crisis') .then((response) => { const html = response.data; const $ = cheerio.load(html); // DEBUG 信息 let loopCounter = 0; const climate_A_Elements = $('a:contains(climate)').length; console.log('找到的文章数量: ' + climate_A_Elements); // 修正选择器,去掉多余参数和转义字符 const allFoundArticles = $('a:contains(climate)'); allFoundArticles.each(function () { // 去除文本前后空格,避免无效内容 const title = $(this).text().trim(); const url = $(this).attr('href'); // 过滤空标题或空URL的条目 if (title && url) { articlesData.push({ title, url, }); } loopCounter += 1; console.log('循环次数: ' + loopCounter); }); // 所有文章遍历完成后,统一发送一次响应 res.json(articlesData); }) // 把catch移到Axios请求链末尾,捕获所有请求/解析错误 .catch(err => { console.error('数据获取或解析出错:', err); // 给客户端返回错误状态码和信息 res.status(500).json({ error: '获取新闻数据失败' }); }); });
关键修复点说明
- 响应时机调整:将
res.json(articlesData)移到each循环外部,确保所有文章数据收集完成后只发送一次响应 - 局部数组声明:在请求处理函数内部声明
articlesData,避免多次请求导致数据累加 - 选择器优化:简化Cheerio选择器,移除冗余参数和转义字符
- 数据过滤:添加空值判断,避免无效条目进入结果数组
- 错误处理修正:调整
catch位置,确保能捕获Axios请求和后续处理中的错误,并返回标准错误响应
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

