Я пытаюсь получить данные в формате дерева из Mongodb рекурсивно, используя Mongoose в приложении Node Express. Я пытаюсь сделать это с помощью обещаний, и я явно делаю это неправильно.
Скажем, у вас есть таблица mongodb foos ':
{
"_id": {
"$oid": "5b5fb27232db7b13a1090577"
},
"bazs": [
{
"$oid": "5b626a2e325c181bdf4fba08"
}
],
"bars": [
{
"$oid": "5b5fb3cc32db7b13a1090579"
}
],
"name": "Parent1",
"accountId": "5b5fb27132db7b13a1090576",
"__v": 0
}
Итак, у этого есть два разных типа дочерних объектов. Кроме того, объекты baz имеют другие объекты baz в качестве дочерних, а также объекты bar. Скажем, это таблица с идентификатором bars:
{
"_id": {
"$oid": "5b626a2e325c181bdf4fba08"
},
"bazs": [
{
"$oid": "5b5fb3cc32db7b75fa01"
}
],
"bars": [
{
"$oid": "5b5fb3c7cdaf740a500a"
}
],
"accountId": "5b5fb27132db7b13a1090576",
"parent": {
"$oid": "5b5fb27232db7b13a1090577"
},
"__v": 0
}
Я использовал рекурсивную функцию в преобразователе, ожидая извлечения типа объекта с помощью Mongoose.
const getObjectRecursive = (type, id)=>{ //'type' will be 'Foo' or 'Bar'
return new Promise((resolve, reject)=>{
type.findOne.call(type, {_id:id},{}, err=>{
if (err){
reject(err);
}
})
.then((rslt)=>{
const result = rslt;
const bars = result.bars;
const bazs = result.bazs;
result.children = {};
result.children.bars = tasks.map(async barId=>{
const barIdString = barId.toString();
await getBarRecursive(barIdString, err=>{
if (err){
reject(err);
}
});
});
result.children.bazs = bazs.map(async eventId=>{
const bazIdString = bazId.toString();
await Baz.findOne({_id:eventIdString}, {},err=>{
if (err){
reject(err);
}
});
});
resolve(result);
})
});
}
И я использую его в этих двух функциях:
const getBarRecursive = (taskId)=>{
return getObjectRecursive(Bar, taskId);
}
const getFooRecursive=(categoryId)=>{
return getObjectRecursive(Foo,categoryId);
};
Которые вызываются этим маршрутом:
router.get('/:id', verifyToken, (req,res)=>{
Branch.getFooRecursive(req.params.id)
.then(
(foo)=>{
res.status(200).send(cat);
},
(err)=>{
res.status(500).send(err);
}
)
});
Это предназначено для рекурсивного возврата объекта дерева и всех дочерних объектов. Происходит то, что дочерние объекты result.children.bars и result.children.bazs становятся обещаниями, которые никогда не выполняются.
Я знаю, что это не лучший способ сделать это. Я явно не выполняю обещания правильно. Я также думал о том, чтобы получить таблицу с помощью $ graphLookup, но я не могу понять, как это сделать, поскольку потомки происходят из разных коллекций.
Как я могу заставить приложение извлекать детей на любую глубину?
ОБНОВЛЕНИЕ: это все еще не работает. Я изменил рекурсивную функцию на это:
const getObjectRecursive = async (type, id)=>{
const rslt = await type.findOne.call(type, {_id:id},{}, {}, err=>{
if (err){
return({err: err});
}
});
const result = rslt.toObject();
const bars = result.bars;
const bazs = result.bazs;
result.children = {};
result.children.bars = tasks.map(async barId=>{
const barIdString = barId.toString();
await getBarRecursive(barIdString, err=>{
if (err){
reject(err);
}
});
});
result.children.bazs = bazs.map(async eventId=>{
const bazIdString = bazId.toString();
await Baz.findOne({_id:eventIdString}, {},err=>{
if (err){
reject(err);
}
});
});
return result
};
В остальном все то же самое. Что происходит сейчас, так это то, что промисы, содержащие результат bar, не выполняются до тех пор, пока ПОСЛЕ того, как результат не будет отправлен клиенту, поэтому я получаю пустой объект на его месте, например:
{
"bars": [
"5b626a2e325c181bdf4fba08"
],
"bazs": [],
"_id": "5b5fb27232db7b13a1090577",
"__v": 0,
"children": {
"bars": [
{}
],
"bazs": []
}
}
Некоторые вещи, которые я пробовал:
bars.map(async barId=>... и т. д. внутри оператора Promises.all и присвоение его result.children в операторе .then(...)bars.map(async barId=>... и т. д. в отдельную асинхронную функцию, а затем скажем result.children.bars = await getAllTasks(tasks)Как я могу убедиться, что мое обещание выполнено до отправки результата?
Значит, вы говорите, что вместо того, чтобы заставить findObjectRecursive вернуть обещание, превратить его и getFooRecursive и getBarRecursive в асинхронные функции?
Вы все равно можете использовать рекурсию, если вам нужно, но с гораздо более чистым потоком управления, который оставляет меньше места для ошибок. const result = await type.findOne(...) и др.
Я попробую это завтра
Итак, мои обещания были выполнены, спасибо. Похоже, у меня есть еще одна проблема, когда материал, который я добавляю к своим возвращаемым объектам, не включается в то, что возвращается, но это что-то еще. Спасибо.
Рад, что помог.
Для всех, кому интересно, проблема заключалась в том, что я пытался добавить свойства к объекту Mongoose, поэтому они не возвращались. Я изменил строку const result = rslt на const result = rslt.toObject(), что превратило ее в простой (не мангуст) объект, чтобы я мог добавлять к нему что-то.
Кстати, это хорошая идея сделать это по умолчанию, если вам не нужно обратное. Делается это как const result = await type.findOne(...).lean().
Так что это все еще не совсем работает, потому что некоторые из моих обещаний не выполняются до тех пор, пока не будет отправлен результат. Проблема описана в обновлении выше.
Здесь много проблем. Я пытался их объяснить. Я не могу гарантировать, что это сработает, потому что логика зависит от реальной структуры данных. Поскольку существует множество запросов и вложенных обещаний, может быть лучше переместить некоторые из этих запросов на сторону Mongo или реструктурировать данные. Обилие findOne указывает на то, что вместо этого может быть один find.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Mongoose уже давно поддерживает обещания, нет необходимости использовать обратные вызовы. Функция reject не определена, ее использование приведет к исключению. Также здесь неоправдан call. Также есть несколько несоответствий: tasks против bars и eventId против bazId. Все они помешают нормальной работе кода
Должно получиться что-то вроде:
const getObjectRecursive = async (type, id)=>{
const result = await type.findOne({_id:id}).lean();
const barsPromises = result.bars.map(barId=>{
return getBarRecursive(barId.toString()); // shouldn't it accept 2 args?
});
const bazsPromises = result.bazs.map(bazId =>{
return Baz.findOne({_id:bazId.toString()}).lean();
});
const [bars, bazs] = await Promise.all([
Promise.all(barsPromises),
Promise.all(bazsPromises)
]);
result.children = { bars, bazs };
return result;
};
В примере активно используется stackoverflow.com/questions/23803743/…. Мангуст поддерживает обещания. Почему вы продолжаете использовать обратный вызов findOne? И вы смешиваете эти вещи с async / await. Проблема, вероятно, была бы решена, если бы вы везде использовали async / await.