NodeJs, javascript: .forEach 似乎是异步的?需要同步

Posted

技术标签:

【中文标题】NodeJs, javascript: .forEach 似乎是异步的?需要同步【英文标题】:NodeJs, javascript: .forEach seems to be asynchronous? need synchronization 【发布时间】: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 循环完成之前发生的方式(这给了我一个错误的在线用户列表)?

非常感谢!

【问题讨论】:

【参考方案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 的信息,这将使您能够以同步方式使用异步函数。但它们并不是那么容易使用。 ;)

以上是关于NodeJs, javascript: .forEach 似乎是异步的?需要同步的主要内容,如果未能解决你的问题,请参考以下文章

Fore xcode 加载包

fore end common url

从函数返回结果(javascript、nodejs)

nodeJs到底是什么?

asp是什么, javascript和php,asp区别,什么是 JavaScript 引擎, nodejs和vuejs的关系,nodejs和javascript区别

Nodejs基础之JavaScript模块化