You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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(&quot;climate&quot;)', 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错误。

另外还有几个小问题:

  1. articlesData如果是全局变量,多次请求会导致数据累加
  2. Cheerio选择器里的&quot;climate&quot;是多余的转义,且不需要在选择器里传入html参数(因为$已经加载了页面内容)
  3. 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: '获取新闻数据失败' });
        });
});

关键修复点说明

  1. 响应时机调整:将res.json(articlesData)移到each循环外部,确保所有文章数据收集完成后只发送一次响应
  2. 局部数组声明:在请求处理函数内部声明articlesData,避免多次请求导致数据累加
  3. 选择器优化:简化Cheerio选择器,移除冗余参数和转义字符
  4. 数据过滤:添加空值判断,避免无效条目进入结果数组
  5. 错误处理修正:调整catch位置,确保能捕获Axios请求和后续处理中的错误,并返回标准错误响应

内容的提问来源于stack exchange,提问作者Dan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 10:25:33