异步 Node.js 循环中的变量范围
Posted
技术标签:
【中文标题】异步 Node.js 循环中的变量范围【英文标题】:Variable Scope in Asynchronous Node.js Loop 【发布时间】:2015-09-20 16:13:04 【问题描述】:我正在尝试在数组上运行一些数据库查询(使用sails.js),并在查询返回时执行某些操作。我认为最好的方法是使用 for 循环并异步解决 Promise,一旦它们全部解决,继续。但是,只有我的数组中的最后一个 promise 正在解析,并且它正在解析多次,因为在每个 'User.findOne...' then 函数中,索引是 array.length-1。
我的问题:
-
异步循环中的变量作用域如何工作?解释这一点的最佳资源?
解决我的问题的最佳方法是什么?为什么?
我应该使用或不使用任何其他模式吗?我对 Promise 和异步 js 还很陌生,所以任何提示都会有所帮助!
我检查过的主要教程
https://github.com/kriskowal/q https://github.com/kriskowal/q/wiki/API-Reference https://github.com/bellbind/using-promise-q/感谢您的帮助!
我的简化代码:
functionWhichReturnsPromise()
.then(function(user)
var promises = [];
Q.try(function()
for (var index in array)
var fbid = array[index];// Get fbid from array
promises.push(Q.defer().promise); // Add promise to promise array
// Find userid from fbid; resolve respective promise when finished
User.findOne(facebook_id: fbid).then(function(userSeen)
promises[index].resolve(userSeen.id);
sails.log('resolved where id=' + userSeen.id); // correct
sails.log('resolved where index=' + index); // PROBLEM: always last index
);
).then(function()
// For debugging purposes
Q.delay(1000).then(function()
sails.log(promises[0]); // Unresolved
sails.log(promises[1]); // Unresolved
sails.log(promises[2]); // Only last promise in array is resolved
);
// When the userids have been extracted from above (promises fulfilled)...
Q.all(promises).then(function(seenids)
// Do stuff here (Doesn't get here)
);
);
);
【问题讨论】:
是的,您使用的是deferred antipattern。此外,这里没有真正的理由使用Q.try
。
@Bergi,我阅读了您关于延迟反模式的参考,但我不太确定如何在没有延迟对象的情况下实现这一点。我有 n 个并行进程要执行,当它们完成后,我需要使用它们的数据。使用 .then 不会对单个承诺起作用,因为我需要等到它们全部完成。我将如何实施呢?我也可以使用 Q() 吗?
你没有使用 deferreds 来等待他们完成 - 你使用 Q.all
(你应该这样做)。 deferreds 仅用于构造 promises
中的每一个,并且不需要它们。你应该只做promises.push(Q(User.findOne(…)).then(function(userSeen) …; return userSeen.id; ))
。你甚至可以省略Q()
调用,因为findOne()
已经返回了一个承诺
@Bergi - 谢谢。我想我明白你的意思,但我无法得到一个更简单的版本来工作,我想我错过了一些东西。我发布了一个新问题,因为我意识到它与这个问题有多么相关,所以也许你可以告诉我你的意思;我很乐意接受你的回答。 ***.com/questions/31551638/…
【参考方案1】:
在 javascript 中,变量的作用域是函数而不是花括号。
因此在下面的代码中,var index
的作用域不是for循环的花括号,作用域实际上是for循环所在的函数。
Q.try(function()
for (var index in array)
var fbid = array[index];// Get fbid from array
promises.push(Q.defer().promise); // Add promise to promise array
// Find userid from fbid; resolve respective promise when finished
User.findOne(facebook_id: fbid).then(function(userSeen)
promises[index].resolve(userSeen.id);
sails.log('resolved where id=' + userSeen.id); // correct
sails.log('resolved where index=' + index); // PROBLEM: always last index
);
)
在 for 循环中,您调用 async 函数,在您的情况下是 mongodb 调用 (findOne
)。
您应该始终假设这些异步函数可能需要任意数量的毫秒才能运行(取决于函数)。但一般来说,循环通常会在异步函数运行之前完成。即使在这些函数开始运行之前,您的 for 循环也会触发所有这些异步函数。问题是所有那些区域挂起的异步函数仍然指向那个变量index
。而且该变量对所有人来说都是通用的,因为index
在外部函数的范围内。
这是由于 Javascript 中的闭包造成的问题。为了解决这个问题,我们需要使用更多的闭包。
您可以在 Google 上搜索很多关于闭包主题的资源。但是请通过MDN's description of it。
如果您在循环内的另一个函数中捕获 index
的值,那么您就可以开始了。
这是我针对您的问题提出的解决方案。虽然我还没有测试过,但你明白了。
Q.try (function ()
array.forEach( function(ele, idx, array)
(function(index)
var fbid = array[index]; // Get fbid from array
promises.push(Q.defer().promise); // Add promise to promise array
// Find userid from fbid; resolve respective promise when finished
User.findOne(
facebook_id : fbid
).then(function (userSeen)
promises[index].resolve(userSeen.id);
sails.log('resolved where id=' + userSeen.id); // correct
sails.log('resolved where index=' + index); // PROBLEM: always last index
);
)(idx);
)
)
希望这会有所帮助。
另请注意: it is incorrect to use for...in for iterating through arrays.
【讨论】:
谢谢!在您的帮助下,我能够解决它,我一定会研究闭包。啊,是的,我忘了 for...in;谢谢你提醒我。以上是关于异步 Node.js 循环中的变量范围的主要内容,如果未能解决你的问题,请参考以下文章