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

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数组,给每个请求生成唯一标识来关联数据:

  1. 安装node-cache:npm install node-cache
  2. 修改后端代码:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:24:31