node.js 中 ForEach 中的异步请求
Asynchronous Request Within a ForEach in node.js
我是 node.js(以及 request.js)的新手。我想从具有不同路径的特定 url 获取网站正文(在下面的示例中 http://www.example.com/path1, http://www.example.com/path2 等),并将此数据记录在具有 [=27= 的对象中] 映射(下面的 siteData[path])。
var request = require('request'),
paths = ['path1','path2','path3'],
siteData = {},
pathLength = paths.length,
pathIndex = 0;
paths.forEach((path) => {
var url="http://www.example.com/"+path;
request(url, function(error, response, html){
if(!error){
siteData[path] = response.body;
pathIndex++;
if(pathIndex===pathLength){
someFunction(siteData);
}
}
});
function someFunction(data){
//manipulate data
}
我的问题是:
- if 语句(索引 === 长度)看起来不像是确定异步请求是否完成的正确方法。我应该如何正确检查请求是否完成?
- 当我执行上面的代码时出现错误
(node) warning: possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit.
我尝试链接 request(url, function(...){}).setMaxListeners(100);
但没有成功。
感谢您的帮助!
由于 nodejs 中 request
方法的异步特性,您无法直接了解它们的响应并实时采取行动。您必须等待回调到达,然后才能调用下一个 request
方法。
在这种情况下,您在 forEach
循环中调用所有 request
方法,这意味着它们将在不等待先前响应的情况下被一个接一个地调用。
我建议为此使用精彩的 async
库,如下所示 -
var async = require('aysnc');
var request = require('request'),
paths = ['path1','path2','path3'],
siteData = {},
pathLength = paths.length,
pathIndex = 0,
count = 0;
async.whilst(
function () { return count < pathLength; },
function (callback) {
// do your request call here
var path = paths[pathLength];
var url="http://www.example.com/"+path;
request(url, function(error, response, html){
if(!error){
siteData[path] = response.body;
// call another request method
count++;
callback();
}
});
},
function (err) {
// all the request calls are finished or an error occurred
// manipulate data here
someFunction(siteData);
}
);
希望对您有所帮助。
看起来 Promises 是完成这里工作的正确工具。我们将创建一个新的 Promise
对象,而不是回调,该对象将在作业完成时解析。我们可以用 .then
运算符说 "once you're done, do some more stuff":
var rp = require('request-promise');
rp('http://www.google.com')
.then((htmlString) => {
// Process html...
});
(如果出现任何问题,承诺 拒绝 并直接进入 .catch
)
someFunctionThatErrors('Yikes!')
.then((data) => {
// won't be called
})
.catch((err) => {
// Will be called, we handle the error here
});
我们有很多异步任务要做,所以只有一个承诺是行不通的。一种选择是将它们串联在一起,如下所示:
rp('http://www.google.com')
.then((htmlString) => rp('http://someOtherUrl.com'))
.then((otherHtmlString) => {
// and so forth...
但这失去了一些令人敬畏的异步 - 我们可以并行.
完成所有这些任务
var myRequests = [];
myRequests.push(rp('http://www.google.com').then(processStuff).catch(handleErr));
myRequests.push(rp('http://someOtherUrl.com').then(processStuff).catch(handleErr));
...男孩看起来很难看。所有这些都有更好的方法 - Promise.all()
(您使用的是箭头函数,所以我认为原生 Promise
也适用于您)。它需要一个 promise 数组和 returns 一个在 all 数组的 promise 完成执行时解析的 promise。 (如果其中任何一个出错,它会立即拒绝)。 .then
函数将被赋予一个 数组 表示每个承诺解决的值。
var myRequests = [];
myRequests.push(rp('http://www.google.com'));
myRequests.push(rp('http://someOtherUrl.com'));
Promise.all(myRequests)
.then((arrayOfHtml) => {
// arrayOfHtml[0] is the results from google,
// arrayOfHtml[1] is the results from someOtherUrl
// ...etc
arrayOfHtml.forEach(processStuff);
})
.catch(/* handle error */);
仍然,我们必须为每个要命中的 link 手动调用 .push
。那不行!让我们使用 Array.prototype.map
来一个巧妙的技巧,它将遍历我们的数组,依次操作每个值和 return 一个由新值组成的新数组:
var arrayOfPromises = paths.map((path) => rp(`http://www.example.com/${path}`));
Promise.all(arrayOfPromises)
.then((arrayOfHtml) => arrayOfHtml.forEach(processStuff))
.catch(function (err) { console.log('agh!'); });
更清晰、更容易的错误处理。
我同意上面的解决方案,在这种情况下,promises 可能是可行的方法;但是,您也可以使用回调来实现相同的目的。
lodash 库提供了方便的方法来跟踪已完成的异步调用数。
'use strict';
var _ = require('lodash');
var path = require('path');
var paths = ['a', 'b', 'c'];
var base = 'www.example.com';
var done = _.after(paths.length, completeAfterDone);
_.forEach(paths, function(part) {
var url = path.join(base, part);
asynchFunction(url, function() {
done();
});
});
function completeAfterDone() {
console.log('Process Complete');
}
function asynchFunction(input, cb) {
setTimeout(function() {
console.log(input);
cb();
}, Math.random() * 5000);
};
使用此方法,done 函数将跟踪已完成的请求数量,并在每个 url 加载后调用最终回调。
根据我的经验,在处理请求模块时不能只使用 forEach 或任何类型的循环,因为它异步执行并最终导致 EventEmitter 内存泄漏。
我解决这个问题的方法是使用递归函数。您可以参考以下代码:
var request = require('request'),
paths = ['path1','path2','path3'],
siteData = {};
function requestSiteData(paths) {
if (paths.length) {
var path = paths.shift();
var url = "http://www.example.com/" + path;
request(url, function(error, response, html) {
if(!error) {
siteData[path] = response.body;
} //add else block if want to terminate when error occur
//continue to process data even if error occur
requestSiteData(paths); //call the same function
});
} else {
someFunction(siteData); //all paths are requested
}
}
function someFunction(data){
//manipulate data
}
requestSiteData(paths); //start requesting data
我是 node.js(以及 request.js)的新手。我想从具有不同路径的特定 url 获取网站正文(在下面的示例中 http://www.example.com/path1, http://www.example.com/path2 等),并将此数据记录在具有 [=27= 的对象中] 映射(下面的 siteData[path])。
var request = require('request'),
paths = ['path1','path2','path3'],
siteData = {},
pathLength = paths.length,
pathIndex = 0;
paths.forEach((path) => {
var url="http://www.example.com/"+path;
request(url, function(error, response, html){
if(!error){
siteData[path] = response.body;
pathIndex++;
if(pathIndex===pathLength){
someFunction(siteData);
}
}
});
function someFunction(data){
//manipulate data
}
我的问题是:
- if 语句(索引 === 长度)看起来不像是确定异步请求是否完成的正确方法。我应该如何正确检查请求是否完成?
- 当我执行上面的代码时出现错误
(node) warning: possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit.
我尝试链接request(url, function(...){}).setMaxListeners(100);
但没有成功。
感谢您的帮助!
由于 nodejs 中 request
方法的异步特性,您无法直接了解它们的响应并实时采取行动。您必须等待回调到达,然后才能调用下一个 request
方法。
在这种情况下,您在 forEach
循环中调用所有 request
方法,这意味着它们将在不等待先前响应的情况下被一个接一个地调用。
我建议为此使用精彩的 async
库,如下所示 -
var async = require('aysnc');
var request = require('request'),
paths = ['path1','path2','path3'],
siteData = {},
pathLength = paths.length,
pathIndex = 0,
count = 0;
async.whilst(
function () { return count < pathLength; },
function (callback) {
// do your request call here
var path = paths[pathLength];
var url="http://www.example.com/"+path;
request(url, function(error, response, html){
if(!error){
siteData[path] = response.body;
// call another request method
count++;
callback();
}
});
},
function (err) {
// all the request calls are finished or an error occurred
// manipulate data here
someFunction(siteData);
}
);
希望对您有所帮助。
看起来 Promises 是完成这里工作的正确工具。我们将创建一个新的 Promise
对象,而不是回调,该对象将在作业完成时解析。我们可以用 .then
运算符说 "once you're done, do some more stuff":
var rp = require('request-promise');
rp('http://www.google.com')
.then((htmlString) => {
// Process html...
});
(如果出现任何问题,承诺 拒绝 并直接进入 .catch
)
someFunctionThatErrors('Yikes!')
.then((data) => {
// won't be called
})
.catch((err) => {
// Will be called, we handle the error here
});
我们有很多异步任务要做,所以只有一个承诺是行不通的。一种选择是将它们串联在一起,如下所示:
rp('http://www.google.com')
.then((htmlString) => rp('http://someOtherUrl.com'))
.then((otherHtmlString) => {
// and so forth...
但这失去了一些令人敬畏的异步 - 我们可以并行.
完成所有这些任务var myRequests = [];
myRequests.push(rp('http://www.google.com').then(processStuff).catch(handleErr));
myRequests.push(rp('http://someOtherUrl.com').then(processStuff).catch(handleErr));
...男孩看起来很难看。所有这些都有更好的方法 - Promise.all()
(您使用的是箭头函数,所以我认为原生 Promise
也适用于您)。它需要一个 promise 数组和 returns 一个在 all 数组的 promise 完成执行时解析的 promise。 (如果其中任何一个出错,它会立即拒绝)。 .then
函数将被赋予一个 数组 表示每个承诺解决的值。
var myRequests = [];
myRequests.push(rp('http://www.google.com'));
myRequests.push(rp('http://someOtherUrl.com'));
Promise.all(myRequests)
.then((arrayOfHtml) => {
// arrayOfHtml[0] is the results from google,
// arrayOfHtml[1] is the results from someOtherUrl
// ...etc
arrayOfHtml.forEach(processStuff);
})
.catch(/* handle error */);
仍然,我们必须为每个要命中的 link 手动调用 .push
。那不行!让我们使用 Array.prototype.map
来一个巧妙的技巧,它将遍历我们的数组,依次操作每个值和 return 一个由新值组成的新数组:
var arrayOfPromises = paths.map((path) => rp(`http://www.example.com/${path}`));
Promise.all(arrayOfPromises)
.then((arrayOfHtml) => arrayOfHtml.forEach(processStuff))
.catch(function (err) { console.log('agh!'); });
更清晰、更容易的错误处理。
我同意上面的解决方案,在这种情况下,promises 可能是可行的方法;但是,您也可以使用回调来实现相同的目的。
lodash 库提供了方便的方法来跟踪已完成的异步调用数。
'use strict';
var _ = require('lodash');
var path = require('path');
var paths = ['a', 'b', 'c'];
var base = 'www.example.com';
var done = _.after(paths.length, completeAfterDone);
_.forEach(paths, function(part) {
var url = path.join(base, part);
asynchFunction(url, function() {
done();
});
});
function completeAfterDone() {
console.log('Process Complete');
}
function asynchFunction(input, cb) {
setTimeout(function() {
console.log(input);
cb();
}, Math.random() * 5000);
};
使用此方法,done 函数将跟踪已完成的请求数量,并在每个 url 加载后调用最终回调。
根据我的经验,在处理请求模块时不能只使用 forEach 或任何类型的循环,因为它异步执行并最终导致 EventEmitter 内存泄漏。
我解决这个问题的方法是使用递归函数。您可以参考以下代码:
var request = require('request'),
paths = ['path1','path2','path3'],
siteData = {};
function requestSiteData(paths) {
if (paths.length) {
var path = paths.shift();
var url = "http://www.example.com/" + path;
request(url, function(error, response, html) {
if(!error) {
siteData[path] = response.body;
} //add else block if want to terminate when error occur
//continue to process data even if error occur
requestSiteData(paths); //call the same function
});
} else {
someFunction(siteData); //all paths are requested
}
}
function someFunction(data){
//manipulate data
}
requestSiteData(paths); //start requesting data