【问题标题】:Looping & Nesting Promises循环和嵌套 Promise
【发布时间】:2024-01-22 07:35:01
【问题描述】:

我正在使用Request-Promisecheerio 来抓取一些网站数据,基本上我正在尝试实现以下目标:

  1. 创建一个空数组
  2. 登录
  3. 从一页中获取一些信息并将对象推送到数组中
  4. 从另一个页面获取一些信息并将对象推送到数组中
  5. 对于数组中的每个 now 对象,我需要:
    • 转到存储在该对象中的 URL {link: "some url", items: []}
    • 遍历该链接中找到的所有项目,并将其推送到迭代对象中的项目数组,如下所示:{link: "some url", items: [{item},{item}]}
  6. 访问已完成的 orderArray,应该会输出如下内容:
{link: "some url", items: [{item},{item}]},
{link: "some url", items: [{item},{item}]},
{link: "some url", items: [{item},{item}]}

第 6 步 是我遇到问题的地方,我不知道如何做到这一点,除非根据我的代码将 promise 嵌套在 for 循环中,然后开始变得讨厌。我可以在这里指出正确的方向吗?

这是我当前的代码:

    let orderArray = [];

    rp.post(login)

    .then(function(res1){

        // Login & Set Cookies
        cookieJar = res1.headers['set-cookie'];

        return rp(getOpenOrders);

    })

    .then(function($){

        // Get Some Info from getOpenOrders

        orderArray.push({info});

        return rp(getShippedOrders);

    })

    .then(function($){

        // Get Some Info from getShippedOrders

        orderArray.push({info});

        return orderArray;

    })

    .then(function($){

        // Loop through each object in the orderArray
        for (i = 0,; i < orderArray.length; i++){

            rp(orderArray[I].link)

            .then(function($){

            //Get length of items on page
            let itemsOnPage = $('tbody tr').length;

            //Get some more details for each object
            for (j = 0,; j < items.length; j++) {
                    let moreinfo = {…};
                    orderArray.items.push(moreinfo);
            }

          }
        }

        return orderArray;

    })

    .then(function($){

        // Log finished Array
        console.log(orderArray);

    })

    .catch(function(err){
        console.log(err);
    })

    };

【问题讨论】:

    标签: node.js web-scraping promise request-promise


    【解决方案1】:

    最简单、最干净的方法是使用async/await。该代码不会并行运行(除非我们等待 Promise.all

    .then(async() => {
    
        // Loop through each object in the orderArray
        for(let i = 0; i < orderArray.length; i++) {
    
            // Some may argue no await inside loop...
            // We wait for rp to resolve, it looks like
            // synchronous code so it's easy to understand
            const $ = await rp(orderArray[i].link);
    
            let items = $('tbody tr');
    
            for(const item of items) {
                let moreinfo = {};
                orderArray[i].items.push(moreinfo);
            }
    
        }
    
        return orderArray;
    })
    

    您也可以使用Promise.all 并行发送所有请求,并在它们全部完成后处理结果。

    .then(() => {
    
        // Loop through each object in the orderArray
    
        const requests = [];
    
        for(const order of orderArray) {
            // Push each promise
            requests.push(
                rp(order.link)
            );
        }
    
        // This will resolve when every request finishes
        // If one fails, it will reject, going to `.catch`
        return Promise.all(requests); 
    })
    .then(results => {
    
        // Results is an array containing each individual request result.
    
        results.forEach(($, i) => {
    
            //Get length of items on page
            let items = $('tbody tr');
    
            //Get some more details for each object
            for(const item of items) {
                let moreinfo = {};
                orderArray[i].items.push(moreinfo);
            }
    
        })
    
        return orderArray;
    
    });
    

    我假设rp 解析了一个cheerio 对象,如果没有,请告诉我。

    我无法测试它,因为我没有你的完整代码,但它应该可以工作。

    【讨论】:

    • 非常感谢您提供解决方案,这两种解决方案都非常有效。在我的例子中,我将继续使用第一种方法 (async/await) 因为 Promise.all 似乎从我正在抓取的特定站点吐出 404 错误async/await 没有导致(我假设要解决此问题,我可能需要 .catch 错误的 URI 并使其重试)。再次 - 非常感谢,我正处于放弃的边缘!
    • 很高兴它对您有所帮助!是的,Promise.all 是一个快速失败的功能,所以错误处理很痛苦。我通常使用不会拒绝的包装器。但我会选择异步等待,除非你需要以各种方式并行