0

I'm having trouble finding out why my code never ends execution and gets stuck. It logs done before doing anything in the async run() block. The code just hangs with an output of:

done
test
test
async function successfullyPaid(doc){
    return await updateMongoDBStuff();
}

async function run() {
    findMongoDBstuff();

    await cursor.forEach(async (doc) => { //forEach being Mongo's cursor iterator, not the Native JS one
        console.log('test')
        if (doc.plan.nextPayment <= getUnixTimeSeconds()) {
            if (parseInt(doc.plan.remaining) >= 0){

            allProds = client.db('ProductsDB').collection('allProds')

            let prod = await allProds.findOne({})

            amount = prod.recurring.periods[doc.plan.period]

            let data = {
            "transaction": {
                "amount" : "1000",
                "payment_method_token": `visa_card`,
                "currency_code": "USD",
                "retain_on_success": true,
                "email" : doc.client.email}}
            let response = await superagent.post(`https://example.com`).send(data).set('accept', 'json').auth('xxxxxxxxxxxxxxxxxxxxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxxxxxx');

            if (response.body.transaction.succeeded == true){
                await successfullyPaid(doc)
            }
            else{
                console.log(response)
            }
    }}});
}

run().catch((error)=>{console.log(error);}).then(console.log('done'));

Any ideas?

  • 1
    Does this answer your question? [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Swiffy Jun 16 '22 at 09:13
  • I would change that `await cursor.forEach(async (doc) => {` bit to `await Promise.all(cursor.map(async ...` kind of pattern to make sure all resolve before continuing. – Juho Vepsäläinen Jun 16 '22 at 09:17
  • @JuhoVepsäläinen that does not work. I believe it is because this is Mongo's forEach iterator, not JS's. I feel like I should've made that clear, apologies. await Promise.all(cursor.map(async (doc) => { console.log(doc)})); `object is not iterable (cannot read property Symbol(Symbol.iterator))` – Vibeman1987 Jun 16 '22 at 09:21
  • Ah, I see. It feels like the basic problem is that you have a race condition in your code so you would have to use a coding pattern that avoids that. You could also wrap the forEach code to some function that's tracking completion and resolves on completion (some promise code). – Juho Vepsäläinen Jun 16 '22 at 10:22

1 Answers1

0

According to the MongoDB NodeJS driver documentation, cursors (inheriting from AbstractCursor) have an [asyncIterator] symbol. An object that has that symbol implements a function that allows you to use them in a for await...of loop:

for await (const doc of cursor) {
  console.log(test);
  // ...
  await successfullyPaid(doc);
  // ...
}
console.log('done');

A for await...of loop will fetch one item from the cursor, run whatever is in its body, then repeat until the cursor is exhausted. You can use break, continue, try {} catch {} in the body, just like regular loops.

This will process all items after each other in series, which seems to be what you were aiming for.

If you want to run things in parallel, use a library that can take the AsyncIterator and turn it into batches, then process (for example) 5 at a time until it's exhausted. But doing it in series is probably fast enough, it's easier and less complex.

RickN
  • 12,537
  • 4
  • 24
  • 28
  • Unfortunately, this doesn't work: async function run() { const database = client.db("transactionsDB"); const transactions = database.collection("recurring"); const cursor = transactions.find({}); for await (const doc of cursor) { console.log('test') }} run(); – Vibeman1987 Jun 16 '22 at 13:42
  • It just hangs there, not sure what to do. – Vibeman1987 Jun 16 '22 at 13:42
  • Have you made sure to wait until the connection is open? For example, with `database.connect().then(run)`? Copying the code from your comment (and filling in blanks such as database credentials) logs the string "test" three times if I have 3 records. – RickN Jun 16 '22 at 14:11
  • it logs test twice, the number of records I have in my db. – Vibeman1987 Jun 16 '22 at 14:16
  • You have the data per record in variable `doc`, so it works. Or are you confused by the fact that the Node process doesn't end ("just hangs there")? That's because there's still an active connection to the database. Run `database.close()` to close it manually. – RickN Jun 16 '22 at 14:22
  • Thank you so much. I don't know why I did not think of that before. It was a combo of your solution and this comment here! – Vibeman1987 Jun 16 '22 at 15:14