我在learnyounode的帮助下学习了nodejs,我被困在Juggling Async上。我的解决方案除了问题中最困难的部分外 - 它不按顺序打印结果,因为有些请求在其他请求之前完成。我试图在不使用任何帮助程序库的情况下这样做,所以我打了谷歌。我发现了一个关于控制流的精彩教程 - http://book.mixu.net/node/ch7.html但是发现他的Series - 异步for循环的解决方案只能正常工作,因为他的异步函数在返回之前等待了1000毫秒。在我的示例中,时间是可变的,因此您不会以相同的顺序获得结果。然后,该示例将构建一个控制流库,我觉得虽然它可以工作,但它超出了learnyounode赋值的范围。
我尝试过的一些事情是:
制作包含URL,订单和响应数据的对象。我遇到的问题是,当我坐在http.get的回调中时,我不知道我获取数据的URL。这实际上是我尝试过的大多数事情的错误,我不知道如何将回复的数据链接到回调中的特定URL。
使用asyncCounter作为索引,专门为存储阵列中的返回数据设置位置。因此,当事件“结束”发生时,我会按照您在下面的代码中看到的那样设置它。显然这不起作用,因为呼叫在不同时间完成。
最终我陷入困境,看着解决方案。这就是我所拥有的,并用星号标记了我的解决方案中缺少的内容。
var http = require("http");
var storage = [];
var urlList = [];
var asyncCounter = 0;
//Store all the URL's from the command line in an array
for(var i = 2; i < process.argv.length; i++){
urlList.push(process.argv[i]);
}
//This function prints out all the data in the storage array
//The storage array contains the data received from http.get
function AsyncComplete(){
for(var j = 0; j < storage.length; j++){
console.log(storage[j]);
};
};
//Entry function
function main(){
//Do an http get on each of the provided URL's
for(var i = 0; i < urlList.length; i++){
**(function(i){**
http.get(urlList[i], function(response){
var body = "";
//Got a chunk of data!
response.on("data", function(chunk){
body += chunk;
});
//All done with our request.
response.on("end", function (){
//Store the total response from the URL
storage[**i**] = body;
asyncCounter++;
//Check if we can print the results yet
//I want to wait for all the calls to complete so I am using a counter
if(asyncCounter == urlList.length){
AsyncComplete();
}
});
});
**})(i);**
};
};
main();
所以我再次点击Google,因为我不知道他们在做什么,我发现了一些叫做闭包的东西。根据我的理解,他们所做的事情让我处于回调的范围内 - 我无法弄清楚如何做到这一点,想法是不可能的,并且很早就放弃了。
我的问题是这究竟是如何运作的 - 特别是为什么他们有(i)他们在哪里以及服务的目的。没有它(i),解决方案不起作用。为什么你可以抛出(函数(i){某处?这是我在一百万年里从未想过的事情。
我找到https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures,似乎它可以包含答案,所以我会在此期间阅读,看看我是否能想出一些东西。
提前感谢您的时间!
史蒂夫。
答案 0 :(得分:2)
这称为立即调用函数表达式(IIFE),它只是一个立即调用的匿名函数。传递给此IIFE的参数i放在调用堆栈上,与for循环的外部i不同。您可以想象它就像创建当前i的快照一样。
在JavaScript中,使用“var”声明的变量是函数作用域。所以,所有回调都会访问相同的i(这不是你想要的)。通过将其传递给IIFE(这是一个函数,从而为变量i引入自己的范围),为每个回调创建一个新变量。 如果你给IIFE的参数另一个名字,那就更明显了。例如:
(function(savedI) {
// ...
})(i)
通过命名参数i,原始i是阴影。这意味着您无法访问IIFE内部的“外部”。
使用IIFE的常见替代方法是使用 map 。例如:
_.range(urlList.length).map(function(i) {
// ...
})
_.range是来自underscore的实用函数,它给出一个从0到urlList.length的数组。传递的函数将接收一个不会被覆盖的参数i(就像你的版本中的for循环一样)。
避免此问题的另一种可能性是通过 let 声明变量,这在EcmaScript 6或更高版本中是可能的。这引入了块范围变量。您可以阅读更多相关信息here。例如:
for(let i = 0; i < urlList.length; i++){
// ..
}