使用Promises在for循环中调用API的最佳方法

mym*_*and 5 javascript node.js promise bluebird

我有5亿个对象,其中每个都有n个联系人,如下所示

var groupsArray = [
                    {'G1': ['C1','C2','C3'....]},
                    {'G2': ['D1','D2','D3'....]}
                     ...
                    {'G2000': ['D2001','D2002','D2003'....]}
                     ...
                ]
Run Code Online (Sandbox Code Playgroud)

我在nodejs中有两种实现方式,它基于常规promise,另一种方法使用bluebird,如下所示

定期承诺

...
var groupsArray = [
                    {'G1': ['C1','C2','C3']},
                    {'G2': ['D1','D2','D3']}
                ]

function ajax(url) {
  return new Promise(function(resolve, reject) {
        request.get(url,{json: true}, function(error, data) {
            if (error) {
                reject(error);
            } else {
                resolve(data);  
            }
        });
    });
}
_.each(groupsArray,function(groupData){
    _.each(groupData,function(contactlists,groupIndex){
        // console.log(groupIndex)
        _.each(contactlists,function(contactData){
            ajax('http://localhost:3001/api/getcontactdata/'+groupIndex+'/'+contactData).then(function(result) {
                console.log(result.body);
              // Code depending on result
            }).catch(function() {
              // An error occurred
            });
        })
    })
})
...
Run Code Online (Sandbox Code Playgroud)

使用bluebird方式我使用并发来检查如何控制promises队列

...
_.each(groupsArray,function(groupData){
    _.each(groupData,function(contactlists,groupIndex){
        var contacts = [];
        // console.log(groupIndex)
        _.each(contactlists,function(contactData){
            contacts.push({
                contact_name: 'Contact ' + contactData
            });
        })
        groups.push({
            task_name: 'Group ' + groupIndex,
            contacts: contacts
        });
    })
})

Promise.each(groups, group => 
    Promise.map(group.contacts,
         contact => new Promise((resolve, reject) => {
                /*setTimeout(() => 
                    resolve(group.task_name + ' ' + contact.contact_name), 1000);*/
                request.get('http://localhost:3001/api/getcontactdata/'+group.task_name+'/'+contact.contact_name,{json: true}, function(error, data) {
                    if (error) {
                        reject(error);
                    } else {
                        resolve(data);  
                    }
                });
}).then(log => console.log(log.body)), 
{
    concurrency: 50
}).then(() => console.log())).then(() => {
    console.log('All Done!!');
});
...
Run Code Online (Sandbox Code Playgroud)

我想知道什么时候使用promises处理内部循环中的1亿个api调用.请告知以异步方式调用API的最佳方法,稍后再处理响应.

Ash*_*vis 5

我的答案使用常规 Node.js 承诺(这可能很容易适应 Bluebird 或其他库)。

您可以使用以下命令立即触发所有 Promise Promise.all

var groupsArray = [
                    {'G1': ['C1','C2','C3']},
                    {'G2': ['D1','D2','D3']}
                ];


function ajax(url) {
  return new Promise(function(resolve, reject) {
        request.get(url,{json: true}, function(error, data) {
            if (error) {
                reject(error);
            } else {
                resolve(data);  
            }
        });
    });
}

Promise.all(groupsArray.map(group => ajax("your-url-here")))
    .then(results => {
        // Code that depends on all results.
    })
    .catch(err => {
        // Handle the error.
    });
Run Code Online (Sandbox Code Playgroud)

使用Promise.all尝试并行运行所有请求。当您有 5 亿个请求同时尝试时,这可能不会很好地工作!

一种更有效的方法是使用 JavaScriptreduce函数将您的请求一个接一个地排序:

// ... Setup as before ...

const results = [];

groupsArray.reduce((prevPromise, group) => {
            return prevPromise.then(() => {
                return ajax("your-url-here")
                    .then(result => {
                        // Process a single result if necessary.
                        results.push(result); // Collect your results.
                    });
            });
        },
        Promise.resolve() // Seed promise.
    );
    .then(() => {
        // Code that depends on all results.
    })
    .catch(err => {
        // Handle the error.
    });
Run Code Online (Sandbox Code Playgroud)

此示例将承诺链接在一起,以便下一个承诺仅在前一个承诺完成后才开始。

不幸的是,排序方法会非常慢,因为它必须等到每个请求完成后才能开始新的请求。当每个请求正在进行时(发出 API 请求需要时间),您的 CPU 处于空闲状态,而它可能正在处理另一个请求!

解决此问题的更有效但复杂的方法是使用上述方法的组合。您应该对请求进行批处理,以便并行执行每批(例如 10 个)中的请求,然后按顺序对各批进行排序。

自己实现这个功能很棘手 - 尽管这是一个很好的学习练习 - 使用Promise.allreduce函数的组合,但我建议使用该库async-await-parallel。有很多这样的库,但我使用这个,它运行良好并且可以轻松完成您想要的工作。

您可以像这样安装该库:

npm install --save async-await-parallel
Run Code Online (Sandbox Code Playgroud)

以下是您将如何使用它:

const parallel = require("async-await-parallel");

// ... Setup as before ...

const batchSize = 10;

parallel(groupsArray.map(group => {
        return () => { // We need to return a 'thunk' function, so that the jobs can be started when they are need, rather than all at once.
            return ajax("your-url-here");               
        }
    }, batchSize)
    .then(() => {
        // Code that depends on all results.
    })
    .catch(err => {
        // Handle the error.
    });
Run Code Online (Sandbox Code Playgroud)

这更好,但发出如此大量的请求仍然是一种笨拙的方式!也许您需要加大赌注并考虑投入时间进行适当的异步作业管理。

我最近一直在使用Kue来管理工作进程集群。将 Kue 与 Node.js 集群库结合使用可以让您在多核 PC 上获得适当的并行性,然后如果您需要更多的工作,您可以轻松地将其扩展到多个基于云的虚拟机。

请参阅我的回答此处获取一些 Kue 示例代码。

  • 感谢您的简短解释。我最近使用 Kue manager 来实现它 (2认同)