problema assíncrono array vazio nodejs mongoose
desejo obter de um array de IDs que vêm no req.body o nome e preço de cada um dos produtos e salvá-lo em um novo modelo (pagamento), para isso percorro cada produto e procuro seu id no db (Product.findById) e eu salvamos como um objeto em arrayProducts o que preciso (nome e preço), mas o newPayment é criado antes que arrayProducts termine de preencher e eu o crio vazio, como poderia fazer para que newPayment seja criado após o término do forEach dos produtos?
router.post('/new/payment', async (req, res) => {
const date = Date.now();
const { amount, clientId, products, services, officeId } = req.body;
var arrayProducts = [];
products.forEach(function (product) {
var productFound = {};
Product.findById(product, function (err, result) {
if (err) {
res.status(404).send({ error: "Oh uh, something went wrong", err: err });
}
else {
productFound = { name: result.name, price: result.price };
arrayProducts.push(productFound);
console.log(arrayProducts);
}
})
});
console.log(arrayProducts);
const newPayment = new Payment({ amount, clientId, products: arrayProducts, services, officeId, date });
await newPayment.save((err) => {
if (err) {
res.status(404).send({ error: "Oh uh, something went wrong", err: err });
}
else {
res.status(201).send({ status: "Payment successful", id: newPayment._id });
}
});;
});
Respostas
Você precisa ler algumas coisas sobre operações assíncronas sem bloqueio em node.js. Seu Product.findById()é não-bloqueante e assíncrono, o que significa que seu retorno de chamada é chamado LONG depois que todo o seu manipulador de rota terminou de ser executado e, certamente, após o console.log(arrayProducts);e todo o código que vem depois disso.
Como você deseja fazer N solicitações de seu banco de dados e só quer saber quando todas estão concluídas, a maneira mais eficiente de fazer isso seria usar a interface do Promise para seu banco de dados, coletar uma série de promessas de todas as operações do banco de dados e use Promise.all()para informar quando todos estiverem concluídos.
Isso é o que eu sugeriria:
router.post('/new/payment', async (req, res) => {
const date = Date.now();
const { amount, clientId, products, services, officeId } = req.body;
try {
const allProducts = await Promise.all(products.map(async (product) => {
const item = await Product.findById(product);
return {name: item.name, price: item.price};
}));
const newPayment = new Payment({ amount, clientId, products: allProducts, services, officeId, date });
await newPayment.save();
res.status(201).send({ status: "Payment successful", id: newPayment._id });
} catch(err) {
res.status(404).send({ error: "Oh uh, something went wrong", err: err });
}
});
Outras coisas que estão faltando neste código são:
- Validar valores de entrada
amount, clientId, products, services, officeIde enviar erros apropriados se algum deles estiver ausente ou inválido. - O que fazer se for especificado um produto que não foi encontrado em seu banco de dados?
- Um erro 404 pode não ser apropriado se você estiver recebendo um erro de banco de dados (que provavelmente seria um erro 500).