Home > other >  Strange behavior in for-loop in JS/NodeJS
Strange behavior in for-loop in JS/NodeJS

Time:02-15

I'm working on a NodeJS/VueJS app and I encounter some strange behavior and I am not sure if it is something I am doing wrong or something else.

I have 2 arrays, one array of objects that contains campaigns and one array of objects that contains all promotions associated to all campaigns.

Each Campaign object contains, at this point, an empty array called promos, where I want to push all the promotions for each campaign.

for (var i = 0; i < campaigns.length; i  ) {
    for (var j = 0; j < campaignsPromo.length; j  ) {
        if (campaignsPromo.length > 0) {
            if (campaigns[i].IDCampaign == campaignsPromo[j].IDCampaign) {
                if ((campaignsPromo[j].associated == 1) && (campaignsPromo[j].validated == 1)) {
                    campaigns[i].promos.push(campaignsPromo[j]);

                    console.log("Validated Campaign ID "   campaigns[i].IDCampaign);
                    console.log("Promo ID "   campaignsPromo[j].IDPromo);
                } else if ((campaignsPromo[j].associated == 1) && (campaignsPromo[j].validated == 0)) {
                    campaigns[i].unvalidatedPromos  ;

                    console.log("Unvalidated Campaign ID "   campaigns[i].IDCampaign);
                    console.log("Promo ID "   campaignsPromo[j].IDPromo);
                }
            }
        } else {
            console.log("No promos!");
        }
    }
}

At first, the code seems to be doing what is supposed to do and it checks out with my test data set. However, in the end, all campaigns end up having the same promotions.

Campaigns With No Promos: [{"IDCampaign":7,"campaignName":"dadsadafds","startDate":"2022-02-03","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[]},{"IDCampaign":3,"campaignName":"Tarzan 3","startDate":"2022-02-02","endDate":"2022-02-06","unvalidatedPromos":0,"promos":[]},{"IDCampaign":1,"campaignName":"Tarzan","startDate":"2022-02-01","endDate":"2022-03-01","unvalidatedPromos":0,"promos":[]},{"IDCampaign":2,"campaignName":"Tarzan 2","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[]},{"IDCampaign":4,"campaignName":"Tarzan 4","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[]},{"IDCampaign":5,"campaignName":"Jabe 1","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[]},{"IDCampaign":6,"campaignName":"dadsada","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[]},{"IDCampaign":8,"campaignName":"Black Friday","startDate":"2022-02-01","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[]}]

Validated Campaign ID 1
Promo ID 1119
Unvalidated Campaign ID 1
Promo ID 107

Campaigns With Promos: [{"IDCampaign":7,"campaignName":"dadsadafds","startDate":"2022-02-03","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":3,"campaignName":"Tarzan 3","startDate":"2022-02-02","endDate":"2022-02-06","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":1,"campaignName":"Tarzan","startDate":"2022-02-01","endDate":"2022-03-01","unvalidatedPromos":1,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":2,"campaignName":"Tarzan 2","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":4,"campaignName":"Tarzan 4","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":5,"campaignName":"Jabe 1","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":6,"campaignName":"dadsada","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":8,"campaignName":"Black Friday","startDate":"2022-02-01","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]}]

This is the content of campaignsPromo:

[ { IDCampaign: 1,
    IDPromo: 1119,
    promoType: 'CHRISTMASS',
    associated: 1,
    validated: 1,
    promoName: 'Test Promo 1',
    beginDate: '2020-11-26',
    endingDate: '2020-11-29' },
  { IDCampaign: 1,
    IDPromo: 107,
    promoType: 'CHRISTMASS',
    associated: 1,
    validated: 0,
    promoName: 'Test Promo 2',
    beginDate: '2019-12-21',
    endingDate: '2019-12-23' } ]

Any ideas? From where I'm standing, I am not doing anything wrong but it wouldn't be the first time I'm missing the obvious.

PS: Please ignore the fact my campaigns are called "Tarzan".

CodePudding user response:

You didn't share the offending code, so I'll give a generic answer.

When the symptom is that a bunch of all objects seem to have a property that is the same array of items, it is likely caused by exactly that. Each object may be sharing the same array instance.

A typical mistake is something like the following:

var items = [];
var containers = [];
for (let i = 0; i < 3;   i) {
  items.push(i);
  let container = {};
  container.items = items;
  containers.push(container);
}

console.log(containers);

Although one might expect, or even have intended to get 3 objects like this:

[
  { items: [ 0 ] },
  { items: [ 0, 1 ] },
  { items: [ 0, 1, 2 ] }
]

The items array is actually the same instance of an array. And what you actually get is more like:

[
  { items: [ 0, 1, 2 ] },
  { items: [ 0, 1, 2 ] },
  { items: [ 0, 1, 2 ] }
]

In fact, the stack snippet visualizer actually does a better job visualizing this because it outputs:

[
  {
    "items": [
      /**id:3**/
      0,
      1,
      2
    ]
  },
  {
    "items": /**ref:3**/
  },
  {
    "items": /**ref:3**/
  }
]

In this way, it tries to inform you that it is actually the same array by giving it a ref:3 label and just marking the values of the other properties with the same ref:3 label as a comment.

Causes I see typically stem from a misunderstanding of what it means to assign an array to a property. Doing so does not create a copy of the array. Both objects refer to the same array.

This can be strange because: it can appear to be correct in a debugger by inspecting the contents of your array as your are stepping through a loop. Even if we had added log messages like console.log(items) or even console.log(container) inside the loop, we'd probably still not have enough of a clue that something went wrong because the contents of that one array instance change with each iteration of the loop and we can dump versions of it as text that appear to be correct, but then unwittingly change the contents of the array on the next iteration.

But if we log the entire containers array after the loop, you'll find that each object has the same instance of the array. Assining an array isn't bad if it's only one object that gets the array assigned to it, but if you assign the same array to properties of multiple objects, or in a loop, you may run into this problem.

One possible habit-breaker you can try is to inspect all objects in a second loop after your primary loop rather than sneaking your logging code directly into the first loop. It's less efficient, but if you're often making these kinds of mistakes, it can help you find problems and achieve correctness.

Another habit breaker is to console.log(JSON.stringify(foo)) rather than console.log(foo) because console.log in the browser actually remembers the reference of the object and shows you its current contents, rather than its contents at the time it was logged. This is different depending on the platform, where node.js will log as text instead of by reference.

Good luck and be careful!

  • Related