NodeJs, javascript:.forEach кажется асинхронным? нужна синхронизация

В настоящее время я работаю над проектом с тремя друзьями, использующими nodeJs, expressJs, MongoDB, html5,... Поскольку мы довольно новичок в этих технологиях, мы столкнулись с некоторыми проблемами. Большой проблемой, с которой я не могу найти решение, является асинхронное выполнение определенного кода.

Я хочу, чтобы для каждого цикла был закончен, так что у меня есть обновленный список друзей в Интернете, а затем выполнить 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 асинхронный?), ответ заключается в том, что для каждого блокируется, но в мой пример кажется неблокирующим, потому что он выполняет res.render, прежде чем он завершит цикл? Как я могу убедиться, что для каждого из них закончен, поэтому у меня есть обновленный список друзей (и список друзей), который я могу передать, вместо передачи 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
}

Ответ 2

Мне удалось решить что-то подобное, добавив пакет async в мой проект и изменив 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 в vim).

Однако OnlineUser.findOne() скорее всего асинхронен. поэтому даже если вы переместите звонок в нужное место, это не сработает. См. ответ ShadowCloud о том, как решить эту проблему.