Node.js中如何让一个Express路由在另一个路由完成后执行?
解决Express路由依赖异步操作完成的问题
你的核心问题是HTTP请求的无状态性加上异步操作的时序问题:第一个路由里的request调用是异步的,它还没完成填充数组时,第二个路由就可能已经执行了;而且全局的articleUrlArray在多用户请求场景下会直接导致数据混乱,完全不可靠。
下面给你几个可行的解决方案,按推荐程度排序:
方案1:合并路由逻辑(最直接高效)
既然第二个路由完全依赖第一个路由的结果,不如把两个步骤合并成一个路由,一次请求完成所有操作,彻底避免全局变量和时序问题:
const request = require('request'); const cheerio = require('cheerio'); app.get('/news/api/:newsName/full-content', function(req, res) { const API_KEY = 'example'; const techCrunchURL = `https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${API_KEY}`; switch(req.params.newsName) { case 'tech-crunch': // 第一步:获取文章URL列表 request(techCrunchURL, function(err, response, html) { if (err) { return res.status(500).send('Failed to fetch news list'); } const formattedData = JSON.parse(response.body); const articleUrlArray = formattedData.articles.map(article => article.url); // 第二步:批量抓取文章内容 const checkBody = resolve => (err, response, html) => { if (err) { return resolve({ error: 'Failed to fetch article' }); } const $ = cheerio.load(html); const articleContent = $('.article-content').children('p'); const bodyOne = articleContent.eq(0).text(); const bodyTwo = articleContent.eq(1).text(); const isExtensive = bodyOne.split(' ').length > 50; resolve(isExtensive ? { bodyOne } : { bodyOne, bodyTwo }); }; const getArticle = article => new Promise(resolve => request(article, checkBody(resolve))); Promise.all(articleUrlArray.map(getArticle)) .then(data => res.json(data)) .catch(err => res.status(500).send('Failed to process articles')); }); break; default: res.send('Please type in correct news source'); break; } });
这样用户只需要调用/news/api/tech-crunch/full-content就能直接拿到最终处理后的文章内容,不需要分两次请求。
方案2:前端配合分两次请求(保留原路由结构)
如果一定要保留两个独立路由,需要前端先调用第一个路由获取URL数组,再把数组作为参数传给第二个路由(推荐用POST请求的body传递):
后端修改第二个路由:
app.post('/news/news-desc', (req, res) => { const articleUrlArray = req.body.urls; if (!articleUrlArray || !Array.isArray(articleUrlArray)) { return res.status(400).send('Invalid URL array'); } const checkBody = resolve => (err, response, html) => { if (err) { return resolve({ error: 'Failed to fetch article' }); } const $ = cheerio.load(html); const articleContent = $('.article-content').children('p'); const bodyOne = articleContent.eq(0).text(); const bodyTwo = articleContent.eq(1).text(); const isExtensive = bodyOne.split(' ').length > 50; resolve(isExtensive ? { bodyOne } : { bodyOne, bodyTwo }); }; const getArticle = article => new Promise(resolve => request(article, checkBody(resolve))); Promise.all(articleUrlArray.map(getArticle)) .then(data => res.json(data)) .catch(err => res.status(500).send('Failed to process articles')); });
前端逻辑示例:
// 先调用第一个路由获取URL数组 fetch('/news/api/tech-crunch') .then(res => res.json()) .then(newsData => { const urls = newsData.articles.map(item => item.url); // 再调用第二个路由传URL数组 return fetch('/news/news-desc', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ urls }) }); }) .then(res => res.json()) .then(fullContent => { console.log('处理后的文章内容:', fullContent); });
方案3:后端用缓存存储临时数据(适合复杂多请求场景)
如果必须用两个GET路由,可以用内存缓存(比如node-cache)或者Redis存储URL数组,给每个请求生成唯一标识来关联数据:
- 安装
node-cache:npm install node-cache - 修改后端代码:
const NodeCache = require('node-cache'); const myCache = new NodeCache({ stdTTL: 600 }); // 缓存10分钟,避免内存溢出 app.get('/news/api/:newsName', function(req, res) { const API_KEY = 'example'; const techCrunchURL = `https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${API_KEY}`; const requestId = Math.random().toString(36).substring(2, 15); // 生成唯一请求标识 switch(req.params.newsName) { case 'tech-crunch': request(techCrunchURL, function(err, response, html) { if (err) { return res.status(500).send('Failed to fetch news list'); } const formattedData = JSON.parse(response.body); const articleUrlArray = formattedData.articles.map(article => article.url); myCache.set(requestId, articleUrlArray); // 存入缓存 res.json({ requestId, data: formattedData }); // 返回标识给前端 }); break; default: res.send('Please type in correct news source'); break; } }); app.get('/news/news-desc', (req, res) => { const requestId = req.query.requestId; if (!requestId) { return res.status(400).send('Missing requestId'); } const articleUrlArray = myCache.get(requestId); if (!articleUrlArray) { return res.status(404).send('Request expired or not found'); } const checkBody = resolve => (err, response, html) => { if (err) { return resolve({ error: 'Failed to fetch article' }); } const $ = cheerio.load(html); const articleContent = $('.article-content').children('p'); const bodyOne = articleContent.eq(0).text(); const bodyTwo = articleContent.eq(1).text(); const isExtensive = bodyOne.split(' ').length > 50; resolve(isExtensive ? { bodyOne } : { bodyOne, bodyTwo }); }; const getArticle = article => new Promise(resolve => request(article, checkBody(resolve))); Promise.all(articleUrlArray.map(getArticle)) .then(data => res.json(data)) .catch(err => res.status(500).send('Failed to process articles')); });
前端需要先调用第一个路由拿到requestId,再用这个ID调用第二个路由:/news/news-desc?requestId=xxx
额外提示:
- 你当前使用的
request库已经官方废弃,建议换成axios或者node-fetch,这两个库维护活跃且更安全。 - 绝对不要在生产环境使用全局变量存储用户请求相关数据,会导致多用户数据互相覆盖,引发严重的逻辑错误。
内容的提问来源于stack exchange,提问作者Eunicorn
相关产品推荐
相关产品推荐

