异步 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 循环中的变量范围的主要内容,如果未能解决你的问题,请参考以下文章

带你从零学Node.js

JavaScript 中的事件循环和 Node.js 中的异步非阻塞 I/O 有啥区别?

Node.js的循环与异步问题

Node.js中的异步I/O是如何进行的?

Node.JS:如何将变量传递给异步回调? [复制]

一文搞懂Node.js以及浏览器中的事件循环机制