这是我尝试重构我的代码以正确使用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);
});
答案 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
证明代码不清楚。)