gpt4 book ai didi

javascript - 了解 node.js 异步性 - for 循环与嵌套回调

转载 作者:行者123 更新时间:2023-11-30 10:14:36 24 4
gpt4 key购买 nike

我是 nodejs 的新手,正在尝试理解它的异步思想。在下面的代码片段中,我试图从 mongodb 数据库中随机获取两个文档。它工作正常,但由于嵌套的回调函数看起来非常难看。如果我想获取 100 个文档而不是 2 个,那将是一场灾难。

app.get('/api/two', function(req, res){
dataset.count(function(err, count){
var docs = [];
var rand = Math.floor(Math.random() * count);
dataset.findOne({'index':rand}, function(err, doc){
docs.push(doc);
rand = Math.floor(Math.random() * count);
dataset.findOne({'index':rand}, function(err, doc1){
docs.push(doc1);
res.json(docs);
});
});
});
});

所以我尝试使用 for-loop 代替,但是,下面的代码不起作用,我想我误解了异步方法的想法。

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);
dataset.findOne({'index':rand}, function(err, doc){
docs.push(doc);
});
}
res.json(docs);
});
});

任何人都可以帮助我并向我解释为什么它不起作用吗?非常感谢。

最佳答案

谁能帮我解决这个问题并向我解释为什么它不起作用?

tl;dr -- 问题是由在循环完成之前无法完成的异步函数 (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 秒。

这是因为 sleep 中的 while 循环 block ......它迭代直到 time 毫秒过去,然后才将控制权返回给for 循环。

循环异步函数

问题的根源在于 dataset.findOne 是异步的...这意味着它在数据库返回结果之前将控制权传递回循环。 findOne 方法采用创建闭包的回调(匿名 function(err, doc))。

在这里描述闭包超出了这个答案的范围,但是如果你搜索这个网站或使用你最喜欢的搜索引擎搜索“javascript 闭包”,你会得到大量的信息。

不过,底线是异步调用将查询发送到数据库。因为事务需要一些时间并且它有一个可以接受查询结果的回调,所以它将控制权交还给 for 循环。 (重要:这就是 Node 的“事件循环”及其与“异步编程”的交叉点发挥作用的地方。 Node 通过允许这样的异步行为来提供非阻塞环境。)

让我们看一个例子,说明异步问题是如何让我们绊倒的:

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 获取一个函数来调用...所以即使我们只说“等待一毫秒”,这仍然比循环迭代 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、promises 和其他类似的库是一个很好的工具,是因为它们有助于解决您面临的问题。 async 和 promises 可以将在这种情况下创建的“回调 hell ”变成一个相对干净的解决方案......它更容易阅读,更容易看到异步发生的地方以及何时需要进行编辑,您不必担心您处于哪个回调级别/正在编辑/等等。

关于javascript - 了解 node.js 异步性 - for 循环与嵌套回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24559134/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com