problema assíncrono array vazio nodejs mongoose

Oct 19 2020

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

jfriend00 Oct 20 2020 at 03:12

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:

  1. Validar valores de entrada amount, clientId, products, services, officeIde enviar erros apropriados se algum deles estiver ausente ou inválido.
  2. O que fazer se for especificado um produto que não foi encontrado em seu banco de dados?
  3. Um erro 404 pode não ser apropriado se você estiver recebendo um erro de banco de dados (que provavelmente seria um erro 500).