danefondo
danefondo

Reputation: 555

How to wait for loop to finish before saving another document?

Problem

I'm using a for loop to create and save several documents and then push their ids as a reference to another document, which I later try to save.

However, when I execute the code, the final document save occurs before the for loop, which means the references never get saved.

How to get the for loop to finish before saving another document (in my case, the entry)? Or is there an entirely different, better way to do what I'm trying to accomplish?

What I've tried

I tried separating the code into two async-await functions, I also tried separating the code into a function with a callback that calls the other after its done. I'm currently trying to make sense of Promises more deeply. I'm also exploring whether it's possible to set up a flag or a small time delay.

    Template.findById(templateId, function (err, template) {

        if (err) { return console.log(err) }

        let entry = new Entry();
        entry.entryState = "Draft";
        entry.linkedTemplateId = template._id;

        for (var i = 0; i < template.components.length; i++) {

            let component = template.components[i]

            let entryComp = new entryComponent({
                componentOrder: component.componentOrder,
                componentType: component.componentType,
                templateComponentId: component._id,
                entryId: entry._id
            })

            entryComp.save(function(err){
                if (err) { return console.log(err) }
                entry.entryComponents.push(entryComp._id);
            });

        }

        entry.save(function(err){
            if (err) { return console.log(err) }
        });
    })

Upvotes: 0

Views: 432

Answers (2)

cEeNiKc
cEeNiKc

Reputation: 1318

You can use the new "for of" loop in javascript along with async await to simplify your answer:-

 Template.findById(templateId, async function (err, template) {

  try {
      if (err) { return console.log(err) }

        let entry = new Entry();
        entry.entryState = "Draft";
        entry.linkedTemplateId = template._id;

        for(const component of template.components) {
          let entryComp = new entryComponent({
                componentOrder: component.componentOrder,
                componentType: component.componentType,
                templateComponentId: component._id,
                entryId: entry._id
            })

          await entryComp.save();
          entry.entryComponents.push(entryComp._id);
        }

          await entry.save();
  } catch (error) {
    // handle error
  }
})

Another way of doing it is using normal "for loop" with Promise.all and this one will be more fast then the other one as you won't be waiting for one entryComp to save before going further:-

 Template.findById(templateId, async function (err, template) {

  try {
      if (err) { return console.log(err) }

        let entry = new Entry();
        entry.entryState = "Draft";
        entry.linkedTemplateId = template._id;

        const promises = [];

         for (var i = 0; i < template.components.length; i++) {

            let component = template.components[i]

            let entryComp = new entryComponent({
                componentOrder: component.componentOrder,
                componentType: component.componentType,
                templateComponentId: component._id,
                entryId: entry._id
            })

            promises.push(entryComp.save());
            entry.entryComponents.push(entryComp._id);
        }

        await Promise.all(promises);

          await entry.save();
  } catch (error) {
    // handle error
  }
})

Also, if you are performing multi document operations which are dependent on one another the correct way to perform these operations is through mongodb transactions which will provide atomicity. Take a look at them here https://docs.mongodb.com/master/core/transactions/

Upvotes: 1

Michael Sohnen
Michael Sohnen

Reputation: 980

I like to use recursion for this purpose. Though I am very confident that someone else already thought of this before me, I did discover this solution on my own. Since I figured this out on my own, I am not sure if this is the best way to do things. But I have used it and it works well in my applications.

   Template.findById(templateId, function (err, template) {

    if (err) { return console.log(err) }

    let entry = new Entry();
    entry.entryState = "Draft";
    entry.linkedTemplateId = template._id;

    var fsm = function(i){
        if (i<template.components.length)
        {
            let component = template.components[i]

            let entryComp = new entryComponent({
            componentOrder: component.componentOrder,
            componentType: component.componentType,
            templateComponentId: component._id,
            entryId: entry._id
            })

            entryComp.save(function(err){
                if (err) { return console.log(err) }
                entry.entryComponents.push(entryComp._id);

                    //cause the fsm state change when done saving component
                     fsm(i+1)
                });


        }

        else
        {
            //the last index i=length-1 has been exhausted, and now i= length, so
            //so do not save a component, instead save the final entry 
           entry.save(function(err){
                if (err) { return console.log(err) }
                }); 

        }
    }

    //start the fsm
    fsm(0)

})

In short, I would use recursion to solve the problem. However, there may be a concern of exceeding the recursion stack limit if the array of components is very large. Consider limiting the number of items allowed in this application.

Upvotes: 1

Related Questions