Выталкивание массива из forEach и передача в res

#javascript #callback #sails.js

#javascript #обратный вызов #sails.js

Вопрос:

Я новичок как в javascript, так и в sails. Я немного смущен обратным вызовом javascript. Прямо сейчас у моего контроллера есть этот метод:

 test: function(req, res) {
  var arr = [], someId = [15,16,7];
  someId.forEach(function(val, idx) {
    var temp = {};
    User.find({userId: val}).exec(function(err, usr) {
      Profile.find({profileId: usr.profId}).exec(function(err, prof) {
        temp.name = prof.fullName();
        temp.email = usr.email;
        ...
        arr.push(temp);
        res.json(arr);
      })
    })
  })
}
 

Все идет гладко, но ответ json отправляет только первое значение, а не последнее, поэтому я предполагаю, что res.json передает только первое arr.push значение во время выполнения обратного вызова. Как и где мне вызвать res.json , чтобы он выполнялся только при выполнении обратного вызова. Спасибо

Ответ №1:

Вам нужно подождать, пока не будут выполнены все обратные вызовы. Простой способ сделать это: каждый раз Profile.find , когда выполняется обратный вызов, проверяйте, является ли это последним, проверяя длину arr .

 someId.forEach(function(val, idx) {
    User.find({userId: val}).exec(function(err, usr) {
      Profile.find({profileId: usr.profId}).exec(function(err, prof) {
        var temp = {};
        temp.name = prof.fullName();
        temp.email = usr.email;
        ...
        arr.push(temp);

         // if arr is now full, send it; otherwise, wait for more
        if(arr.length == someId.length) { res.json(arr); }
      })
    })
})
 

Однако вы говорите, что не знаете, сколько времени длится «готовый» массив, пока вы не выполните запрос Users . Решение здесь состоит в том, чтобы сначала запросить всех пользователей, а затем запросить профили после:

 var usersProcessedCount = 0, matchedUsers = [];
someId.forEach(function(val, idx) {
    User.find({userId: val, status: 1}).exec(function(err, usr) {
      if(usr != null) { matchedUsers.push(usr); }
      if(  usersProcessedCount == someId.length) { fetchProfiles(); }
    })
})

function fetchProfiles() {
    matchedUsers.forEach(val, idx) {
      Profile.find({profileId: val.profId}).exec(function(err, prof) {
        var temp = {};
        temp.name = prof.fullName();
        temp.email = val.email;
        ...
        arr.push(temp);

         // if arr is now full, send it; otherwise, wait for more
        if(arr.length == matchedUsers.length) { res.json(arr); }
      })
    });
}
 

Здесь мы храним всех пользователей, которые соответствуют критериям, в массиве, а затем, когда мы закончили обработку всех идентификаторов кандидатов, мы вызываем fetchProfiles , который перебирает список подходящих пользователей.

Комментарии:

1. Я уже пробовал это, это работает нормально, но проблема здесь в том, что если вы наложите некоторые ограничения при запросе пользователя. Например. User.find({userId: val}).where({'status': 1}) длина usr не будет соответствовать длине arr. Есть ли другой способ обойти это?

2. @mateeyow Отредактировано, чтобы решить ваши проблемы с фильтрацией: создайте массив отфильтрованных пользователей, а затем выполните выборку профилей на основе этого отфильтрованного списка (и используйте длину этого списка, чтобы определить, когда вы закончите).

3. Я не думаю, что это сработает, поскольку usersProcessedCount будет увеличиваться только до длины usr, и поскольку не у всех пользователей есть статус 1 if( usersProcessedCount == someId.length) , это не будет true

4. @mateeyow Возможно, я неправильно понимаю, как Sails обрабатывает пустые результаты. Я предположил, что запросы, которые не находят совпадения (т. Е. Статус записи не равен 1), Будут запускать обратный вызов со null значением для usr ; таким образом, usetsProcessedCount будет увеличиваться как для совпадающих, так и для несоответствующих запросов. Вы предполагаете, что для несоответствующих записей обратный вызов вообще не будет выполняться? Это кажется неправильным, поскольку вы не можете отличить запрос без результатов от очень медленного запроса.

Ответ №2:

Вы можете упростить свои запросы, используя встроенный синтаксис запроса Waterline: если вы задаете значение критерия для массива, он будет искать все записи, значение которых для этого поля соответствует одному из массива. Итак:

 // Find all users whose ID is in the someId array
User.find({userId: someId}).exec(function(err, users) {

    if (err) {return res.serverError(err);

    // Get the profile IDs of those users using Lodash "pluck"
    var profileIds = _.pluck(users, 'profId');

    // Find all Profile records with those IDs
    Profile.find({profileId: profileIds}).exec(function(err, profiles) {

        if (err) {return res.serverError(err);

        // Use Array.map to transform the profiles array data
        var results = profiles.map(function(profile) {
            // Use Lodash to get the user with this profile ID
            var user = _.find(users, {profId: profile.profileId});
            return {name: profile.fullName(), email: user.email};
        });
        return res.json(results);

    });


});
 

Обратите внимание, что _ (Lodash) по умолчанию глобализируется Sails, поэтому вы можете использовать его в любом из ваших контроллеров, моделей или сервисов.

Альтернативой было бы использование библиотеки async (также глобализированной Sails) для реорганизации кода более линейным способом, а не с использованием вложенных обратных вызовов.

Если вы используете Sails v0.10.x, вы также можете рассмотреть возможность использования ассоциаций для прямой связи записей пользователя и профиля; тогда вы сможете просто сделать:

 User.find({userId: someId}).populate('profile').exec(...);
 

чтобы получить все, что вам нужно.

Ответ №3:

 test: function(req, res) {
  var arr = [], someId = [15,16,7];
  someId.forEach(function(val, idx) {
    var temp = {};
    User.find({userId: val}).exec(function(err, usr) {
      Profile.find({profileId: usr.profId}).exec(function(err, prof) {
        temp.name = prof.fullName();
        temp.email = usr.email;
        ...
        arr.push(temp);
      })
    })
  })
  res.json(arr);
}
 

Вы не дошли до конца цикла. Вы уже передали значение «ARR» после завершения первого элемента

Комментарии:

1. Метод модели exec является асинхронным, поэтому этот код отправляется arr до выполнения любого из обратных вызовов. Ваш код запускает все запросы, но не ожидает выполнения exec обратных вызовов; таким образом, он отправляет обратно arr до того, как он был заполнен какими-либо данными.