2016-03-02 4 views
0

У меня есть следующий код. Используя $ geoNear, я нахожу ближайшую транзитную остановку к заданной координате gps. Как некоторые из вас могут знать, $ geoNear возвращает только местоположение (loc) ближайшей точки. Чтобы получить информацию о ближайшей остановке, я запрашиваю коллекцию STOPS с использованием местоположения.Mongo возвращает undefined, но данные есть

Проблема в том, что она беспорядочно возвращает неопределенные. Когда я запрашиваю стопы из оболочки mongo, используя параметры agency_key и и loc, я подтверждаю, что данные есть. Я подозреваю, что эта проблема может быть вызвана асинхронностью, но я не могу понять, почему. Я попытался упростить свой вопрос до 'вызов async-функции в forEach loop', но у меня пока нет ничего.

Почему он не возвращается?

... 
    TmStop.aggregate([{ 
       $geoNear: { 
       near: [lon, lat], 
       maxDistance: radiusInDegrees, 
       includeLocs: "distance.location", 
       distanceField: "distance.calculated", 
       query: { 
        agency_key: { 
        $in: agencyKeys 
        } 
       } 
       } 
      }, { 
       $project: { 
       route_id: 1, 
       route_type: 1, 
       direction_id: 1, 
       "distance.calculated": 1, 
       "distance.location": 1 
       } 
      }]) 
      .exec(function(e, results) { 
      if (e) { 
      console.log('e->', e.errmsg); 
      var res = "Something went wrong with the database: " + e; 
      cb(e, res); 
      } else if (!e) { 
      if (results.length) { 
       console.log('results->', results.length); 
       var i = 0; 
       results.forEach(function(result, index) { 
       console.log(index, result); 
       Stop.find({ 
        agency_key: { 
         $in: agencyKeys 
        }, 
        loc: result.distance.location 
        }) 
        .exec(function(e, stop) { 
        if (e) { 
         throw new Error('Error getting stop due to:' + e); 
        } 

        var obj = {}; 
        obj.route_id = result.route_id; 
        obj.route_type = result.route_type; 
        obj.direction_id = result.direction_id; 
        obj.distance = result.distance.calculated; 
        obj.locationUsed = result.distance.location; 

        // console.log('### ', index, ' ####'); 
        // console.log('@Stop.find agencyKeys-> ', agencyKeys); 
        // console.log('@Stop.find loc->', result.distance.location); 
        // console.log(stop[0]); 

        obj.stop = stop[0]; 
        objArr.push(obj); 
        i++; 
        if (i === results.length) { 
         cb(e, objArr); 
        } 
        }); 
       }); //end of forEach 
      } else { 
       cb(e, []); 
      } 
      } 
     }); 

ответ

1

Конечно. У вас есть цикл .forEach(), который вызывает функцию async, не дожидаясь завершения (или, что более важно, «всех» вызовов). Таким образом, ваша итерация цикла должна учитывать вызовы завершения внутренней вызываемой функции.

Хороший инструментом для этого является async библиотекой, и в целях безопасности мы позвоним async.mapLimit (так как вы испускаете массив), который позволит определенному количество операций для запуска similtaneously, а не вплоть до го стека (или, возможно, за его пределами с неконтролируемым циклом .forEach() или for).

Так внутри обратного вызова от операции агрегирования, измените код листинга на это вместо:

 // If aggregation query returned anything 
     if (results.length) { 
      console.log('results->', results.length); 

      async.mapLimit(results,10,function(result,callback) { 
       Stop.findOne({ 
        "agency_key": { "$in": agency_keys }, 
        "loc": result.distance.location 
       },function(e,stop) { 
        result.stop = stop;  // remember that result is already a plain object 
        callback(e,result); 
       }); 
      },cb)    // cb is automatically passed e and results 
     } else { 
      cb(e,results); // then the array was empty 
     } 

Есть несколько улучшений там, в первую очередь в том, что в то время как вы имели дело с Mongoose документов вернулся из регулярные операции .find(), вы, возможно, заметили, что вы просто не можете добавлять к ним новые свойства, поскольку они являются документами mongoosse (более сложным объектом), а не обычными объектами. Они просто притворяются одними из методов сериализации и вспомогательного доступа.

Но результаты .aggregate() на самом деле просто «простой объект». Поэтому нет необходимости копировать свойства объекта в новый объект только для назначения нового свойства. Для справок в будущем вам не нужно делать это для «Mongoose documents» так явно, как в вашем списке, но просто назовите .toObject() на документе. Результат возвращает «простой объект». Но в коде, который нужен здесь, это просто простое назначение.

Также используется .findOne(), а не .find(). В вашем коде отмечено, что вы все равно ищете сингулярный матч и ссылаетесь на первый элемент результатов. Поэтому просто попросите один результат. Сам запрос также, вероятно, вернет один результат из-за сингулярного точного местоположения от «ближнего» и без других параметров запроса, но у меня действительно нет этой информации.

Наконец, конечно, есть .map() или скорее .mapLimit(), для которого после всех ваших намерений, чтобы просто добавить новое свойство к каждому элементу массива results из данных Stop, а затем возвращает этот элемент с дополнительным свойством. Каждая итерация будет выдавать поставленные callback с измененными данными, затем по завершении всей итерации вызывается окончательный обратный вызов, который будет содержать испущенный массив всех возвращенных элементов.Это в основном встроенные аргументы вашего cb, поэтому он просто поставляется в этой форме, а не обертывается другой функцией.

+0

Большое спасибо за этот подробный ответ. Я не знал, что агрегированные функции возвращают простой объект, в отличие от документов мангуста. Это очень ценная информация. Я знаком с асинхронной библиотекой. Я хотел попробовать написать эту часть без асинхронной библиотеки, но ясно, что этот подход не очень хорошо работает для меня. Еще раз спасибо. – melis

+0

@melis Вы «могли» сделать что-то подобное с обещаниями вместо этого (в основном «Promise.all (array_of_queries)»), но часть «limit» (что в большинстве случаев является хорошей идеей) займет немного «ручного проката» "реализовать. Если вы на самом деле не готовы писать много своих собственных слоев библиотеки для таких вещей, обычно это меньшее зло, чтобы просто включить другую зависимость для того, что уже обрабатывает ее. –

Смежные вопросы