【问题标题】:NodeJs, javascript: .forEach seems to be asynchronous? need synchronizationNodeJs, javascript: .forEach 似乎是异步的?需要同步
【发布时间】:2012-05-21 16:57:45
【问题描述】:

我目前正在与 3 个朋友一起使用 nodeJs、expressJs、MongoDB、html5、... 由于我们对这些技术还很陌生,因此遇到了一些问题。 我找不到解决方案的一个大问题是某些代码的异步执行。

我想要一个 for each 循环完成,这样我就有一个更新的在线朋友列表,然后执行 res.render(我在其中传递在线朋友列表),因为目前它在它之前执行 res.render完成循环。 代码:

function onlineFriends(req, res) {
var onlinefriends = new Array();
onlinefriends.push("mark");
FriendList.findOne({
    owner: req.session.username
}, function (err, friendlist) {
    friendlist.friends.forEach(function (friend) { // here forEach starts
        OnlineUser.findOne({
            userName: friend
        }, function (err, onlineFriend) {
            if (onlineFriend != null) {
                onlinefriends.push(onlineFriend.userName);
                console.log("a loop");
            }
        });

    });  
        console.log("online friends: " + onlinefriends);
        console.log("redirecting");
        res.render('index', { // this is still inside the forEach function
            friendlist: friendlist.friends,
            onlinefriendlist: onlinefriends,
            username: req.session.username
        });// and here it ends
});

}

输出如下:

online friends: mark
redirecting
a loop
a loop
a loop
a loop
a loop
a loop
a loop

正如这里所讨论的(JavaScript, Node.js: is Array.forEach asynchronous?),答案是 for-each 是阻塞的,但在我的示例中,它似乎是非阻塞的,因为它在完成循环之前执行 res.render? 如何确保 for each 已完成,以便我有一个最新的 onlinefriends 列表(和friendlist),然后我可以将其传递给 res.render 而不是 res.render 在 for -each 循环完成之前发生的方式(这给了我一个错误的在线用户列表)?

非常感谢!

【问题讨论】:

    标签: javascript node.js asynchronous foreach synchronous


    【解决方案1】:

    以下控制台日志:

    console.log("a loop");
    

    在回调中

    我相信函数OnlineUser.findOne()的回调是异步调用的,这就是为什么代码会在重定向日志之后记录“一个循环”

    你应该在所有的循环回调都执行完之后再进行重定向

    类似:

    var count = 0;
    friendlist.friends.forEach(function (friend) { // here forEach starts
        OnlineUser.findOne({
            userName: friend
        }, function (err, onlineFriend) {
            count++;
            if (onlineFriend != null) {
                onlinefriends.push(onlineFriend.userName);
                console.log("a loop");
            }
            if(count == friendlist.friends.length) { // check if all callbacks have been called
                redirect();
            }
        });
    }); 
    
    function redirect() {
        console.log("online friends: " + onlinefriends);
        console.log("redirecting");
        res.render('index', { // this is still inside the forEach function
            friendlist: friendlist.friends,
            onlinefriendlist: onlinefriends,
                username: req.session.username
        });// and here it ends
    }
    

    【讨论】:

    • 谢谢!!这行得通,但是这种在 javascript 中的编程是否被视为“坏习惯”?或者以这种方式工作是否完全合法?
    • 这不是一个坏习惯,这就是 javascript 的工作方式,你只需要习惯回调。无论如何,这显然不是最干净的方式,你可以构建自己的函数,包装函数,或使用类似:github.com/coolaj86/futures/tree/v2.0/forEachAsync,这也保证了函数回调的顺序(我提供的代码没有)
    • 该解决方案有效,但大多数开发人员会使用 async.JS 或 Promise 库 (github.com/kriskowal/q)。其他库可以为您的代码提供更多“杠杆”。
    【解决方案2】:

    我能够通过将异步包添加到我的项目并将 forEach() 更改为 async.each() 来解决类似的问题。这样做的好处是为应用程序的其他部分提供了一种标准的同步方式。

    你的项目是这样的:

    function onlineFriends(req, res) {
      var onlinefriends = new Array();
      onlinefriends.push("mark");
    
      FriendList.findOne({owner: req.session.username}, function (err, friendlist) {
        async.each(friendlist.friends, function(friend, callback) {
          OnlineUser.findOne({userName: friend}, function (err, onlineFriend) {
            if (onlineFriend != null) {
              onlinefriends.push(onlineFriend.userName);
              console.log("a loop");
            }
            callback();
          });
        }, function(err) {
          console.log("online friends: " + onlinefriends);
          console.log("redirecting");
          res.render('index', { // this is still inside the forEach function
              friendlist: friendlist.friends,
              onlinefriendlist: onlinefriends,
              username: req.session.username
          });
        });
      });
    }
    

    【讨论】:

    • 这是采用前进的最佳解决方案,无需担心变量或命令回调触发。谢谢!
    【解决方案3】:

    通过jsbeautifier 运行您的代码可以正确缩进并向您展示发生这种情况的原因:

    function onlineFriends(req, res) {
        var onlinefriends = new Array();
        onlinefriends.push("mark");
        FriendList.findOne({
            owner: req.session.username
        }, function (err, friendlist) {
            friendlist.friends.forEach(function (friend) { // here forEach starts
                console.log("vriend: " + friend);
                OnlineUser.findOne({
                    userName: friend
                }, function (err, onlineFriend) {
                    if (onlineFriend != null) {
                        onlinefriends.push(onlineFriend.userName);
                        console.log("online friends: " + onlinefriends);
                    }
                });
                console.log("nu door verwijzen");
                res.render('index', { // this is still inside the forEach function
                    friendlist: friendlist.friends,
                    onlinefriendlist: onlinefriends,
                    username: req.session.username
                });
            });  // and here it ends
        });
    

    所以...总是正确缩进你的代码,你不会有这样的问题。一些编辑器(例如 Vim)可以使用单个快捷方式(gg=G in vim)缩进整个文件。

    但是,OnlineUser.findOne() 很可能是异步的。因此,即使您将呼叫移动到正确的位置,它也不起作用。请参阅ShadowCloud's answer 了解如何解决此问题。

    【讨论】:

    • 因为我一直在测试解决方案,我已经尝试将代码广告放在循环的末尾,没有任何区别
    • 为什么不把res.render(...) 调用放在 foreach 的末尾,但放在第一个回调中。比重定向将在 foreach 完成后执行。
    • @graydsl 我不知道您的确切意思,但我想我尝试了大多数地方,它在循环之前一直在进行渲染:o
    • @Jeroen 忘记我说的话。我错了,因为OnlineUser.findOne(...) 电话。嗯..如果 ShadowCload 的答案对你有用,那很好。 :) 您可以阅读有关fibers 的信息,这将使您能够以同步方式使用异步函数。但它们并不是那么容易使用。 ;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-10
    • 2017-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多