如何让Axios等待内部函数执行完成再返回res.json?
问题解决思路与代码示例
核心问题分析
- 异步时序问题:
res.json在异步的getElementsCheerio执行完成前就触发,导致返回空结果——本质是没有正确等待异步操作完成就返回响应。 - 重复数据问题:
map+Promise.all返回重复数据,大概率是闭包变量共享导致所有异步操作复用了同一个报纸对象引用,而非当前迭代项。
具体解决方案
1. 改造getElementsCheerio为异步函数
将原本依赖全局变量或回调的逻辑,改为返回Promise的异步函数,确保每个调用独立处理单份报纸的内容:
const axios = require('axios'); const cheerio = require('cheerio'); // 接收单个报纸对象,返回该报纸的新闻数组Promise async function getElementsCheerio(newspaper) { // 请求报纸页面(如果你的逻辑里需要这一步) const pageRes = await axios.get(newspaper.url); const $ = cheerio.load(pageRes.data); const currentArticles = []; // 示例:抓取前2条新闻,根据你的实际选择器调整 $('.news-item').each((idx, el) => { if (idx < 2) { currentArticles.push({ title: $(el).find('h2').text().trim(), link: `${newspaper.baseUrl || ''}${$(el).find('a').attr('href')}`, source: newspaper.name }); } }); return currentArticles; }
2. 用async/await+Promise.all统一处理所有报纸
在路由处理函数中,用async标记函数,通过Promise.all等待所有报纸的爬取任务完成后,再返回合并结果:
// 假设你用Express,路由示例 app.get('/fetch-news', async (req, res) => { // 你的报纸列表,替换成实际数据 const newspapers = [ { name: 'XX日报', url: 'https://xxx.com/news', baseUrl: 'https://xxx.com' }, { name: 'YY晚报', url: 'https://yyy.com/latest', baseUrl: 'https://yyy.com' } ]; try { // 遍历每个报纸,生成独立的爬取Promise const allNewsList = await Promise.all( newspapers.map(async (newspaper) => { // 此处用async函数确保每个迭代项独立处理,避免闭包变量共享 return getElementsCheerio(newspaper); }) ); // 合并所有报纸的新闻为一维数组(按需调整) const mergedNews = allNewsList.flat(); res.json({ code: 200, data: mergedNews }); } catch (err) { console.error('爬取失败:', err); res.status(500).json({ code: 500, msg: '爬取失败' }); } });
关键注意事项
- 绝对不要用全局变量存储异步结果:改用函数返回值+
Promise收集结果,避免异步时序问题。 map回调里必须用const声明当前迭代的newspaper:确保每个异步操作引用的是当前循环的独立对象,而非共享变量。- 所有异步操作必须被
await或Promise.all包裹:确保res.json在所有爬取任务完成后才执行。
内容的提问来源于stack exchange,提问作者Bella
相关产品推荐
相关产品推荐

