异步 Node.js 循环中的变量作用域

Variable Scope in Asynchronous Node.js Loop

我正在尝试 运行 对数组进行一些数据库查询(使用 sails.js),并根据查询 return 执行某些操作。我认为最好的方法是使用 for 循环并异步解决 promises,一旦它们全部解决,就继续。但是,只有我的数组中的最后一个承诺正在解析,并且它正在解析多次,因为在每个 'User.findOne...' then 函数中,索引是 array.length-1.

我的问题:

  1. 异步循环中的变量作用域如何工作?对此进行解释的最佳资源?
  2. 解决我的问题的最佳方法是什么?为什么?
  3. 还有其他我应该使用或不使用的模式吗?我对 promises 和 async js 还很陌生,所以任何提示都会有所帮助!

我查看的主要教程

感谢您的帮助!


我的简化代码:

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)
    });
  });
});

在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 循环中调用异步函数,在您的例子中是 mongodb 调用 (findOne)。 您应该始终假设这些异步函数可以花费任意数量的毫秒数到 运行(取决于函数)。但一般来说,通常循环会在异步函数 运行 之前完成。您的 for 循环甚至在这些函数开始 运行ning 之前就触发了所有这些异步函数。问题是所有那些挂起的异步函数仍然指向那个变量 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.