0

I'm trying to wait for a forEach to finish, and the forEach loop has two nested requests inside. I need to wait untill the forEach finish beacuse I fill an array with the queries results and then, when the forEach is finish, then call another function, but I cannot do it well because sometimes, the array is fully filled, but othertimes the array is incomplete.

Here is my code:

readAllClientsAndInvoices: function(request, response) {
    let clientsInvoices = [];
    DAOClients.readAllClientesById(request.session.id, function (err, clients) {
        if (err) {
            console.log(err);
        } else {
            clients.forEach(function (client, idx, array) {
                DAOClients.readClientDataById(client.id, function (err, data) {
                    if (err) {
                        console.log(err)
                    } else {
                        DAOClients.readAllclientInvoices(data.id, function (err, invoices) {
                            if (err) {
                                console.log(err);
                            } else {
                                let pair = {
                                    clientData: data,
                                    invoicesList: invoices
                                };
                                clientsInvoices.push(pair);
                            }
                        });
                    }
                    if (idx === array.length - 1) {
                        DAOClients.createClientPDFReportWOCommentsV2(clientsInvoices, function (err, res) {
                            if (err) {
                                console.log(err);
                            } else {
                                response.redirect(307, '/client/searchClient');
                            }
                        });
                    }
                });
            });
        }
    });
}

This is how I do it now, but I need to wait untill the array is fully filled with all the clients and its invoices and then call to createclientPDFReportWOCommentsV2 function but I don't know how to do it.

Thanks everyone

Geshode
  • 3,600
  • 6
  • 18
  • 32
Dozer
  • 17
  • 5
  • foreach loop doesn't work well when you want to do asynchronous operations inside a loop's body. try "for of" or simple "for" loop to run iterations. See, https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop – Kartik Chauhan Jul 24 '20 at 07:06
  • You can use "for(const [idx, client] of clients.entries()) {}" to get the index of the current iteration. – Kartik Chauhan Jul 24 '20 at 07:21

2 Answers2

0

You can try to use a map instead of forEach in order to accept a return value from every call of the callback function, that return value will have to be a Promise, resolving after particular call has been completed. Since I don't see any particular error handling in your example I just made it so that in case of error Promise resolves undefined which is filtered afterwards in the createClientPDFReportWOCommentsV2 call.

function readAllClientsAndInvoices(request, response) {
    DAOClients.readAllClientesById(request.session.id, function (err, clients) {
        if (err) return console.log(err);
        Promise.all(clients.map(client => {
            return new Promise(resolve => {
                DAOClients.readClientDataById(client.id, function (err, data) {
                    if (err) {
                        console.log(err)
                        resolve();
                    } else {
                        DAOClients.readAllclientInvoices(data.id, function (err, invoices) {
                            if (err) {
                                console.log(err);
                                resolve();
                            } else {
                                let pair = {
                                    clientData: data,
                                    invoicesList: invoices
                                };
                                resolve(pair);
                            }
                        });
                    }
                });
            });
        })).then(clientsInvoices => {
            DAOClients.createClientPDFReportWOCommentsV2(clientsInvoices.filter(Boolean), function (err, res) {
                if (err) {
                    console.log(err);
                } else {
                    response.redirect(307, '/client/searchClient');
                }
            });
        });
    });
}
Krzysztof Krzeszewski
  • 5,912
  • 2
  • 17
  • 30
0

To solve these problems i would use Async/Await https://javascript.info/async-await. Make sure all the methods you're calling on DAOClients returns a Promise https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

For example

function readAllClientesById() {
    return new Promise((resolve, reject) => {
        // Wait for some data to get fetched from SQL
        // and call resolve instead of callback function
        resolve(data)

        // Or of there was an error
        reject(err)
    })

}

This is natively supported in the latest versions of Node.js.

Example of Async/Await if promises is implemented:

async function readAllClientsAndInvoices(req, res) {
    try {
        const clientInvoices = []
        const clients = await DAOClients.readAllClientesById(req.session.id)

        for (const client of clients) {
            const clientData = await DAOClients.readClientDataById(client.id)
            const clientInvoices = await DAOClients.readAllclientInvoices(clientData.id)

            clientInvoices.push({
                clientData: data,
                invoicesList: invoices
            })
        }

        // This code won't be executed until the for loop is completed
        await DAOClients.createClientPDFReportWOCommentsV2(clientInvoices)
    } catch (err) {
        return res.status(err.code).send(err)
    }

    res.redirect(307, '/client/searchClient');
}

I haven't tested the code, it's just an example of how I approach these type of problems.

Jonathan Nielsen
  • 1,442
  • 1
  • 17
  • 31