【问题标题】:node.js - Control a queue of Promisesnode.js - 控制 Promises 队列
【发布时间】:2016-08-08 22:14:54
【问题描述】:

我正在编写一个爬虫,它将使用 node.js 从电子商务网站获取数据。我要获取的每个输入都包含:

  • url:该链接的网址
  • directory: 以后应该写入输出文件的目录名
  • page:要查询的参数

每个页面会抓取一些item,每一个都会在后面详细抓取

这是我的 fetchPage 承诺(agentrequire('superagent')),它将获取 HTML 文本:

function fetchPage(url,page){
    return new Promise(
        (resolve,reject)=>{
            if (page>0){
                agent
                .get(url)
                .send('page='+page)
                .end(function(err,res){
                    if (err){
                        reject(err);
                    } else{
                        resolve(res.text);
                    }
                });
            } else{
                agent
                .get(url)
                .end(function(err,res){
                    if (err){
                        reject(err);
                    } else{
                        resolve(res.text);
                    }
                });
            }

        });
}

全球调用:

var data=[];
for (var i=1;i<=links[0].numOfPages;i++){
    data.push({
        url:links[0].url,
        directory:links[0].directory,
        page:i
    });
}

const promises=data.reduce(
    (promise,data)=>promise.then(()=>{
        fetchPage(data.url,data.page).then(
            (result)=>{
                const urls=getUrls(result);
                Promise.all(urls.map((url,i)=>fetchPage(url,0).then(
                        (result)=>{
                            var item=getItem(result);
                            item.url=url;
                            writeItem(item,data.directory,data.page,i+1);
                        },
                        (error)=>console.log(error)
                )));
            });
    }),
    Promise.resolve());

promises.then((values)=>console.log('All done'));

您将看到 3 个功能作为实用程序(它们都可以正常工作):

  • getUrls: 处理一个页面的 HTML 文本,返回一个 url 的数组 稍后详细抓取的项目
  • getItem: 处理一个 HTML 文本 item的详细页面,返回一个将被写入的对象 文件
  • writeItem:将对象写入文件,提供目录 和页码以制作正确的目录并写入和存储

我遇到了一个问题:

  • 如何使用一个 promise 队列重建它,其中每个 promise 会一个接一个的有序运行 同步并且只允许有限数量的 Promise 并发运行?

如何正确高效地完成它?我应该如何使用这些当前代码进行更改?我也需要一些演示

我删除了fetchItem函数,因为它没有必要(实际上是用page = 0调用fetchPage),现在我只使用fetchPage

【问题讨论】:

标签: javascript node.js asynchronous promise web-crawler


【解决方案1】:

首先,如果你想真正控制你的执行,那么你不应该构造一个循环来调用一个promise。它将立即执行。相反,您应该构造一些数据来提供给 Promise。抱歉,我不太了解您的程序流程。我可以看到您正在调用fetchPage,在它完成后,调用fetchItem,它再次调用fetchPage。这可能就是你收到双重回调的原因。

对于第二个问题,这里有一个示例,说明如何串行处理每个链接,并以最多 3 个并发作业并行处理链接中的页面。

var Promise = require('bluebird');
var chance = new (require('chance'))();

var fetchPage = (url, page) => new Promise((resolve, reject) => {
    // Simulate Network Operation
    if (page === 0) {
        console.log('Start Downloading: ' + url);
        setTimeout(() => {
            resolve({
                url: url,
                content: 'Content of ' + url
            });
        }, chance.integer({ min: 10, max: 250 }));
    } else {
        console.log('Start Downloading: ' + url + '?page=' + page);
        setTimeout(() => {
            resolve({
                url: url + '?page=' + page,
                content: 'Content of ' + url + '?page=' + page
            });
        }, chance.integer({ min: 10, max: 250 }));
    }
});

var fetchItem = link => {
    // Get the data to be supplied to fetchPage promise
    var data = [];
    for (var i = 0; i <= link.numOfPages; i++) {
        data.push({
            url: link.url,
            page: i
        });
    }
    return data;
};

var writeItem = (item, directory) => {
    // Simulate Writing to Directory
    console.log('Writing ' + item + ' to ' + directory + ' folder');
};

// Make some dummy links
var links = [];
for (var i = 0; i < 10; i++) {
    var domain = chance.domain();
    links.push({
        url: chance.url({ domain: domain }),
        directory: domain,
        numOfPages: chance.integer({ min: 0, max: 5 })
    });
}

// Process each URL serially
Promise.each(links, link => Promise.map(fetchItem(link), data => fetchPage(data.url, data.page).then(result => {
    writeItem(result.content, link.directory);
    console.log('Done Fetching: ' + result.url);
}), {
    // Control the number of concurrent job
    concurrency: 3
})).then(() => {
    console.log('All Done!!');
});

更新:一个更简单的例子来演示Promise.eachPromise.map

var Promise = require('bluebird');
var chance = new (require('chance'))();

var tasks = [];

for (var i = 1; i <= chance.integer({ min: 10, max: 20 }); i++) {
    var jobs = [];
    for (var j = 1; j <= chance.integer({ min: 2, max: 10 }); j++) {
        jobs.push({
            job_name: 'Job ' + j
        });
    }

    tasks.push({
        task_name: 'Task ' + i,
        jobs: jobs
    });
}

Promise.each(tasks, task => Promise.map(task.jobs, job => new Promise((resolve, reject) => {
    setTimeout(() => resolve(task.task_name + ' ' + job.job_name), chance.integer({ min: 20, max: 150 }));
}).then(log => console.log(log)), {
    concurrency: 3
}).then(() => console.log())).then(() => {
    console.log('All Done!!');
});

在这个例子中你可以清楚地看到每个任务是按顺序运行的,并且一个任务中的每个作业都是并行运行的,一次最多有 3 个并发作业。

【讨论】:

  • 我删除了fetchItem函数,因为它没有必要(实际上,它用page = 0调用fetchPage),现在我只使用fetchPage并且我做了一些改变,但事情似乎没有按顺序运行我还为您编辑了问题以更好地了解我的流程
  • 如果要按顺序运行,请使用Promise.each。否则,如果您想与有限数量的并发作业同时运行,请使用Promise.map 并为函数提供concurrency 选项,如我上面的示例所示
  • 关于Promise.each,让我试试,这对我来说是新的。谢谢
  • 不客气。只要确保您使用的是bluebird,否则您不能使用Promise.eachPromise.map。并尝试运行我的示例以获得更好的理解。
  • 我正在使用bluebird。但我对Promise.eachPromise.all 之间的区别感到困惑。据我了解,Promise.all 一个接一个地运行一系列承诺
【解决方案2】:

对于您的情况,我建议您安装 Bluebird Promise 库,因为它提供了一些您可以使用的实用程序。

对于您的问题,通常情况下,您不会将 for 循环与 Promise 结合使用,而是构造一个数据数组,以及一个返回 Promise 的映射函数,然后将 .map() + Promise.all().reduce() 数组转换为单个 Promise,当一切都完成后解决。

Bluebird 的Promise.map() 还允许您指定并发选项,这将限制可以同时运行的操作数。


以下是一些帮助您入门的示例:

同时运行异步操作

const Promise = require('bluebird');
const urls = ['https://url1.com', 'https://url2.com', ... ]; // lots of urls
// {concurrency: 4} means only 4 URLs are processed at any given time.
const allPromise = Promise.map(urls, fetchUrlAsync, {concurrency: 4});
allPromise.then(allValues => {
  // Deal with all results in order of original array
});

按顺序运行异步操作:

const Promise = require('bluebird');
const urls = ['https://url1.com', 'https://url2.com', ... ]; // lots of urls
// {concurrency: 4} means only 4 URLs are processed at any given time.
const allPromise = urls.reduce((promise, url) => 
  // Start with an empty promise, chain all calls on top of that
  promise.then(() => fetchUrlAsync(url)), Promise.resolve()); 
allPromise.then(allValues => {
  // Deal with all results in order of original array
});

尝试将事物视为值的集合,以及您对这些值执行的操作,将您的操作抽象为函数,并在适当的时候调用它们,不要在同一个地方混合获取和写入。

【讨论】:

  • 我删除了fetchItem函数,因为它没有必要(实际上,它用page = 0调用fetchPage),现在我只使用fetchPage并且我做了一些改变,但事情似乎没有顺序运行
猜你喜欢
  • 2021-11-10
  • 1970-01-01
  • 1970-01-01
  • 2014-01-27
  • 2019-01-14
  • 2016-01-21
  • 2015-07-12
  • 2018-08-05
  • 1970-01-01
相关资源
最近更新 更多