Search code examples
javascriptarraysangularjsjavascript-objects

push to array within javascript object


function v1 executes w/o error, and console log shows the expected array populated w/ the response data. However, I'm trying to make life simpler down the road by returning 2 arrays within nysQueryReturn as an object:

function v2 also executes without errors, but console logs show

nysQueryReturn {sldlBills: Array(0), slduBills: Array(0)}
...empty arrays.

Function v1: works as expected

function getBillData() {
  return getBills().
  then(function(response) {
    // save retrieved bill numbers
    var billData = response;
    var nysQueryReturn = [];

    // get NY State Leg data for each bill number
    billData.forEach(function(p) {

      // Assembly bill
      nysBillQuery(p.gsx$sessionyear.$t, p.gsx$assemblynum.$t).
      then(function(response){
        nysQueryReturn.push(response);
      });

      // Senate bill
      nysBillQuery(p.gsx$sessionyear.$t, p.gsx$senatenum.$t).
      then(function(response){
        nysQueryReturn.push(response);
      });
    });
    console.log('nysQueryReturn', nysQueryReturn);
    return nysQueryReturn;
  });
} // end of getBillData()

function v2: empty arrays :(

function getBillData() {
  return getBills().
  then(function(response) {
    // save retrieved bill numbers
    var billData = response;
    var nysQueryReturn = {
      sldlBills: [],
      slduBills: []
    };

    // get NY State Leg data for each bill number
    billData.forEach(function(p) {

      // Assembly bill
      nysBillQuery(p.gsx$sessionyear.$t, p.gsx$assemblynum.$t).
      then(function(response){
        nysQueryReturn.sldlBills.push(response);
      });

      // Senate bill
      nysBillQuery(p.gsx$sessionyear.$t, p.gsx$senatenum.$t).
      then(function(response){
        nysQueryReturn.slduBills.push(response);
      });
    });
    console.log('nysQueryReturn', nysQueryReturn);
    return nysQueryReturn;
  });
} // end of getBillData()

i've found several examples of "array of arrays" and "array of objects" on stackoverflow, but i can't see how to re-purpose those answers to fit my "object of arrays" scenario. any thoughts/pointers/explanations of what i'm missing would be warmly welcomed.

Thank you for your time.

edit:

k, I found this question & answer, which seems to suggest I was "doing it right". Taking another look, Chrome Dev Tools console reports that the two arrays are "empty", but when expanded they contain the expected info. Still, I can't actually access the array elements w/ nysQueryReturn.sldlBills[0].basePrintNo without getting TypeError: Cannot read property 'basePrintNo' of undefined, and I can't for the life of me figure out why.

What am I not getting?


Solution

  • I will assume you know about arrow functions and how to use them appropriately. I will also assume you know about let and const. None of these are required, they just make things a little prettier. You can replace all arrow functions (in the example below) with normal functions and all let and const declarations with var declarations.

    Your end result should look something like the following:

    function getBillData() {
      return getBills().then((billData) => {
        const nysQueryReturn = {
          sldlBills: [],
          slduBills: []
        };
    
        // This should look familiar, it returns a Promise.  This
        // Promise first loads the Assembly bill then adds the result
        // to the appropriate array in nysQueryReturn object.
        const loadAssemblyBill = (bill) => {
          return nysBillQuery(bill.gsx$sessionyear.$t, bill.gsx$assemblynum.$t).then((sldlBill) => {
            nysQueryReturn.sldlBills.push(sldlBill);
          });
        };
    
        // This should look familiar, it returns a Promise.  This
        // Promise first loads the Senate bill then adds the result to
        // the appropriate array in nysQueryReturn object.
        const loadSenateBill = (bill) => {
          return nysBillQuery(bill.gsx$sessionyear.$t, bill.gsx$senatenum.$t).then((slduBill) => {
            nysQueryReturn.slduBills.push(slduBill);
          });
        };
    
        // First exciting thing: Let's map each bill to a 2 workers
        // that will load the important information that we will add to
        // nysQueryReturn.
        const workers = [];
    
        billData.forEach((bill) => {
          workers.push(loadAssemblyBill(bill));
          workers.push(loadSenateBill(bill));
        });
    
        // Return a Promise that will wait for all the workers to
        // finish.
        return Promise.all(workers).then(() => nysQueryReturn);
      });
    }
    

    You weren't seeing the results you expected because you weren't waiting for the results to load. In fact, if you set a timeout and examine the result sometime later, you would have seen the arrays populate.

    Let's think of nysQueryReturn as a box that holds all the sldlBills and slduBills, Promises as workers, and the code that called getBillData() as your customer. With v2 of getBillData(), you

    1. Found all the bills.
    2. Created the nysQueryReturn box
    3. Hired some workers and told them what to do.
    4. Gave the box to your customer.

    Unfortunately, you did not wait for your workers to finish their job before you gave the box to your customer. Needless to say, your customer was quite confused and just pretended like they got what they wanted.

    With the new implementation, you

    1. Found all the bills.
    2. Created the nysQueryReturn box
    3. Hired some workers and told them what to do.
    4. Wait for your workers to give you what they found.
    5. Gave the box to your customer.

    You wait for your workers to finish by maintaining a list of the workers and then waiting for all() (Promise.all()) of them to tell you they have finished and added their results to the nysQueryReturn box. After they have all finished, you give your customer all of the results (.then(() => nysQueryReturn)).

    Remember, every time you use a Promise (anything that has a .then() method), you are executing something outside of the flow of the normal program. JS will not wait for that flow to finish before continuing with it's original flow. Pictorially, this would look something like:

    ___________                     ____________                                ____________
    |  Flow 1 |                     |  Flow 2  |                                |  Flow 3  |
    -----------                     ------------                                ------------
    
    billData.forEach(/* ... */);
    console.log(/*...*/);
    return nysQueryReturn;
                                    nysQueryReturn.sldlBills.push(/*...*/);
                                                                                nysQueryReturn.slduBills.push(/*...*/)
    

    To wait for the new flow to finish, you have to explicitly wait for it by passing a callback to .then().