为什么Promise.all中的承诺仍然未决?

时间:2016-09-26 21:59:34

标签: javascript node.js web-scraping promise

这是我尝试重构我的代码以正确使用promises。整个程序是一个基本的webscraper。

这方面的挑战是尝试确保lastStep可以访问每个页面的HTML和网址,以便我尝试在nextStep()中返回一个对象。

我在控制台中记录了html,并且正确地返回了它,但出于某种原因,承诺的记录如下:Promise { <pending> }。为什么会发生这种情况,我该如何解决?

谢谢!

//Modules being used:
var cheerio = require('cheerio');
var json2csv = require('json2csv');
var request = require('request');
var moment = require('moment');
var fs = require('fs');

//harcoded url
var url = 'http://shirts4mike.com/';

//url for tshirt pages
var urlSet = new Set();

var remainder;
var tshirtArray = [];


const requestPromise = function(url) {
    return new Promise(function(resolve, reject) {
        request(url, function(error, response, html) {

            if(error) return reject(error);

            if(!error && response.statusCode == 200){
                return resolve(html);   
            }       
        });
    });
}


function scrape (url) {
    return requestPromise(url)
        .then(function(html) {
            var $ = cheerio.load(html);

            var links = [];

            //get all the links
            $('a[href*=shirt]').each(function(){
                var a = $(this).attr('href');

                //add into link array
                links.push(url + a);
            });
            // return array of links
            return links;
        });
}


function nextStep (arrayOfLinks) { 
    var promiseArray = [];

    for(var i = 0; i < arrayOfLinks.length; i++){
        promiseArray.push(requestPromise(arrayOfLinks[i]));
        var promises = Promise.all(promiseArray);
        console.log(promises);
    }

    return {arrayOfHtml: promises , arrayOfUrls: arrayOfLinks};                 
}


function lastStep (obj){ 
    for(var i = 0;  i < obj.arrayOfHtml.length; i++){
        var $ = cheerio.load(obj.arrayOfHtml[i]);

        //if page has a submit it must be a product page
        if($('[type=submit]').length !== 0){

            //add page to set
            urlSet.add(obj.arrayOfUrls[i]);
            console.log(obj.arrayOfUrls[i]);

        } else if(remainder == undefined) {
            //if not a product page, add it to remainder so it another scrape can be performed.
            remainder = obj.arrayOfUrls[i];
            console.log("remainder: " + remainder);                         
        }
    }
}


scrape(url)
    .then(nextStep)
    .then(lastStep)
    .catch(function(err) {
        // handle any error from any request here
        console.log(err);
     });

2 个答案:

答案 0 :(得分:1)

你可以试试几件事。首先,在requestPromise函数中,当您调用'resolve()'和reject()时,无需返回。我不知道这是否会有所作为,但你至少可以试试。

接下来,正如评论中所讨论的,您应该更改拒绝和解决请求承诺的方式。最简单的说法是:

if(error) {
    reject(error);
} else {
    resolve(html);
}

假设没有错误(错误只发生在4xx或5xx状态代码中),但状态代码不是200?您可以获得2xx或3xx范围内的任何内容而不会出现错误,在这种情况下,requestPromise永远不会被解析或拒绝。这肯定会引起你的问题,因为所有的承诺都必须以一个或另一个结束。

下一期是nextStep。我会重构如下:

function nextStep (arrayOfLinks) { 
    var promiseArray = [];

    for(var i = 0; i < arrayOfLinks.length; i++){
        promiseArray.push(requestPromise(arrayOfLinks[i]));
    }

    return Promise.all(promiseArray)
          .then(function (arrayOfHtml) {
            return {arrayOfHtml: promises , arrayOfUrls: arrayOfLinks};
          });                
} 

使用Promise.all,您希望首先填充您的承诺数组,然后在完成所有异步调用之后,就在您调用Promise.all(promisesArray)时。 then末尾的额外all将获取您的promises数组生成的html,然后将其作为承诺与arrayOfLinks一起返回到您的承诺链中的下一步,在这种情况下,您的lastStep

如果这一切都没有解决您的问题,您将需要回顾状态代码问题,我在状态代码为202之前遇到了问题,这意味着请求被接受,但请求的处理不是不完整。 (您可以阅读有关HTTP状态代码here)的更多信息。这是一个非常相似的情况,我们有一堆网址,我们正在提出要求。我们最终将所有获得202的网址放回tryAgain数组,然后再尝试再次点击它们。

在你的情况下,你可以通过几种方式解决它。最简单的事情是拒绝除200之外的所有状态代码的承诺,这将是一个有点严格的。您可以做的另一件事是,如果没有错误并且状态代码不是200,那么您可以使用某些特殊值解决承诺,或者只是非200状态代码,这将表示您需要再次尝试。然后在nextStep之后,您可以过滤使用非200代码解析的所有结果,并尝试再次点击它们。之后,您可以使用lastStep完成。如果你已经尝试了其他一切而且没有一个有效,我会尝试其中一种解决方案。但这需要一些努力。

希望这会有所帮助。如果您有任何问题,请告诉我。

答案 1 :(得分:1)

您的代码中有一个未处理的else

if(error) return reject(error);

if(!error && response.statusCode == 200){
    return resolve(html);   
}

让我们重新安排更清楚。由于return上面的代码与此完全相同:

if(error) {
    reject(error);
}
else if (response.statusCode == 200) {
    resolve(html);   
}
else {
    // keep this promise pending FOREVER!!
}

你还没有处理最后的其他事情。根据您的意图,您可以进行的最小修改是:

if(error) return reject(error);

if(!error && response.statusCode == 200){
    return resolve(html);   
}

reject(new Error('Not code 200'));

if(error) return reject(error);

if(!error && response.statusCode == 200){
    return resolve(html);   
}

resolve(html);

尽管如此,我个人重写逻辑更清楚(事实上你错过了最后的else证明代码不清楚。)