20

I'm new to node and javascript and have been banging my head on the following. I've created an object as follows:

var Subscriber = {
'userID': String,
'email': String,
'name': String,
'stage': String,
'poster': Boolean,
'canEmail': Boolean,
'stage': String, }

I have a function where I query mongodb, and loop through the results, attempting to load an array of subscribers, which I've declared as:

var s = Subscriber;
var subscribers = [];

The loop looks like this:

//load array of users that are subscribed to the group
        async.forEach(g.subscribers, function(item, callback) {     
            //load user document for this user
            User.findOne({ _id: item}, function(err, u) {
                if(!err && u) {                 
                    //var s = new Subscriber();
                    console.log('Sub load, found user %s, building array item', u.email);
                    console.log('Subs @ loop start');
                    console.log(util.inspect(subscribers));

                    console.log('Heres foo: ' + util.inspect(foo));


                    s.userID = u._id;
                    s.email = u.email;
                    s.name = u.firstName + ' ' + u.lastName;
                    s.stage = u.stage;
                    s.poster = false; //we're just loading subscribers at this point'
                    if(s.stage != 'new') s.canEmail = true;

                    //push new subscriber onto the array
                    console.log('Pushing ' + util.inspect(s));
                    subscribers.push(s);

                    console.log('At end ' + util.inspect(subscribers));

                    foo.push(s.email);
                    console.log('Heres foo now: ' + util.inspect(foo));

                    callback(null, item);
                }

After each call to subscribers.push(s), the array has the correct number of elements, but all elements match the last values for s, like this (with two different users being pulled from the DB):

[ { userID: 4fc53a71163006ed0f000002,
email: 'test@test.com',
name: 'undefined undefined',
stage: 'new',
poster: false,
canEmail: true },
  { userID: 4fc53a71163006ed0f000002,
email: 'test@test.com',
name: 'undefined undefined',
stage: 'new',
poster: false,
canEmail: true } ]

Pushing a single element of s rather than the whole object seems to be fine. I added the "foo" array as a test, and it works fine:

Heres foo now: [ 'email1@foo.com', 'test@test.com' ]

What is going on here?!?!??!

pat
  • 3,513
  • 3
  • 17
  • 20
  • What does `g.subscribers` look like? – alessioalex Jun 07 '12 at 13:18
  • 3
    Objects and arrays (which are objects) are passed by reference in JavaScript. If `s` is an object, and you are re-using it by only changing the properties and then pushing the same object onto the array in a loop, then the objects in the array are all references to the same object. – Steve Jun 07 '12 at 13:25
  • 2
    Thanks! This was very helpful. I was thinking it might be something with references but just couldn't wrap my head around it. I guess this is what happens when you're last dev work was two decades ago in cutting edge languages like Pascal and C! – pat Jun 07 '12 at 14:58
  • Related: [Array.prototype.fill() with object passes reference and not new instance](/q/35578478/4642212). – Sebastian Simon Feb 05 '22 at 14:59

2 Answers2

24

The problem is not with the push method of the Array.prototype but with your bindings. You are modifying the same s object in every iteration in your async.foreach block which is actually the same Object as the previously defined Subscriber.

First you should move the declaration of the s variable to the foreach block.

And also if you want to create an object with default values, it should be a function, which returns a new object:

function Subscriber() {
  return {
    'userID':   '',
    'email':    '',
    'name':     '',
    'stage':    '',
    'poster':   false,
    'canEmail': false,
    'stage':    ''
  };
};

And then you can instantiate a Subscriber object like this:

var s = Subscriber();

See this answer or Closures on MDN for more explanation.

Community
  • 1
  • 1
KARASZI István
  • 30,900
  • 8
  • 101
  • 128
  • What a novice mistake I made. This is exactly what I was not seeing. – Ken Ingram Jul 15 '18 at 23:18
  • Thank you! I've been stumped for days until I found this solution. Not only did it resolve my issue but I now better understand closures and lexical scope. – BBi7 Feb 16 '19 at 13:18
  • Oh my god, i have the same problem in diferent scenario. Thanks for the solution. – lewisrm Apr 04 '19 at 21:09
2

Cloning the object before pushing into the array, also solves the problem.

temp = clone(s);
subscribers.push(temp);

Get https://www.npmjs.com/package/clone

Misa Lazovic
  • 2,805
  • 10
  • 32
  • 38
Ben
  • 61
  • 1