任何人都可以帮助我,并向我解释为什么它不起作用?
TL;博士 - 该问题是通过在一个异步函数(dataset.findOne
)运行的环路引起的,可以在循环完成之前不完整。您需要像async
(正如其他答案所建议的)那样使用库,或者像第一个代码示例中那样使用回调来处理此问题。
循环执行的同步功能
这可以听起来迂腐,但要了解在同步和异步世界循环之间的区别是很重要的。考虑这个同步带:
var numbers = [];
for(i = 0 ; i < 5 ; i++){
numbers[i] = i*2;
}
console.log("array:",numbers);
在我的系统,该电源输出:
array: [ 0, 2, 4, 6, 8 ]
这是因为分配给numbers[i]
发生之前的循环能够迭代。对于任何同步(“阻塞”)分配/功能,您将以这种方式得到结果。
为了说明,让我们试试这个代码:
function sleep(time){
var stop = new Date().getTime();
while(new Date().getTime() < stop + time) {}
}
for(i = 0 ; i < 5 ; i++){
sleep(1000);
}
如果您的手表,或在一些console.log
消息抛出,你会看到“休眠”,持续5秒。
这是因为while
循环在sleep
块......它迭代直到time
毫秒已经超过,然后再返回到for循环控制。
循环通过异步函数
你的问题的根源在于dataset.findOne
是异步的...这意味着它就把控制权回到循环之前的数据库返回的结果。方法findOne
采取回调(匿名function(err, doc)
)创建一个闭包。
描述闭包在这里超出了这个答案的范围,但如果你搜索本网站或使用你最喜欢的搜索引擎“JavaScript闭包”,你会得到吨信息。
但是,底线是异步调用将查询发送到数据库。因为事务需要一些时间并且它有一个可以接受查询结果的回调函数,所以它将控制权交给for循环。 (重要的是:这是节点的“事件循环”和它与“异步编程”的交集,节点通过允许异步行为提供非阻塞环境)。
让我们来看一个例子异步问题可以绊倒我们:
for(i = 0 ; i < 5 ; i++){
setTimeout(
function(){console.log("I think I is: ", i);} // anonymous callback
,1 // wait 1ms before using the callback function
)
}
console.log("I am done executing.")
你会得到输出,看起来像这样:
I am done executing.
I think I is: 5
I think I is: 5
I think I is: 5
I think I is: 5
I think I is: 5
这是因为setTimeout
得到一个函数调用...所以,即使我们只说“等待一毫秒“,那仍然是lo比循环重复5次并移动到最后的console.log
行要花费更多的时间。
然后会发生什么,最后一行在之前触发,第一个匿名回调触发。当它确实发生火灾时,循环已经结束,并且i
等于5
。因此,您在此看到的是循环已完成,并且继续前进,即使交给setTimeout
的匿名函数仍可访问i
的值。 (这是行动中的“关闭”...)
如果我们采用这个概念并使用它来考虑你的第二个“破”代码示例,我们可以看到你为什么没有得到你期望的结果。
app.get('/api/two', function(req, res){
dataset.count(function(err, count){
var docs = []
for(i = 0; i < 2 ; i++){
var rand = Math.floor(Math.random() * count);
// THIS IS ASYNCHRONOUS.
// findOne gets a callback...
// hands control back to the for loop...
// and later pushes info into the "doc" array...
// too late for res.json, at least...
dataset.findOne({'index':rand}, function(err, doc){
docs.push(doc);
});
}
// THE LOOP HAS ENDED BEFORE any of the findOne callbacks fire...
// There's nothing in 'docs' to be sent back to the client. :(
res.json(docs);
});
});
原因async
,承诺和其他类似的库是一个很好的工具是他们帮助解决你所面临的问题。 async
和承诺可以将在这种情况下创建的“回调地狱”变成一个相对干净的解决方案...它更容易阅读,更容易看到异步情况发生的地方,以及当你需要进行编辑时,你没有担心你在/编辑/等等的回调级别。
非常感谢你的详细解释! – Idealist