0

I'm having an awful time tracking this one down.

...

for offset in [1..3]
  queryDate = moment().subtract(offset, 'days')
  console.log offset    # gives 1, 2, 3

  # check if a row already exists for this day
  Datum.findOne { date: { $lte: queryDate.toDate() } }, (err, datum) ->
    console.log offset # gives 4, 4, 4

...

What is the proper way to scope variables so they can be used inside callbacks?

Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
mz3
  • 1,314
  • 11
  • 27

2 Answers2

1

This is actually more of a closure problem than a scope problem. What happens is that when you define a function in javascript ( coffeescript too, of course ) the function remembers context it was created in, including all variables in parent scopes. But what is has are not copies of variables but references of those variables.

Callback from Datum.findOne is going to be called after your for loop has finished iterating. This means the offset variable has already been incremented. You can easily prevent this be wrapping an annonymous function around Datum.findOne, like this:

for offset in [1..3]
  do ( offset = offset ) ->  
    Datum.findOne { date: { $lte: queryDate.toDate() } }, (err, datum) ->
      console.log offset

Passing a variable as an argument to function will create it's copy.

I belive that defining the function inside loop is better for readabilty, but if the function is big, or if the loop has many iterations it's actually better to define it elswhere.

//edit: Actually, coffeescript will define it outside the loop.

You might want to refer to this question to read up on closures.

Community
  • 1
  • 1
zir
  • 223
  • 1
  • 2
  • 10
0

Using a wrapping function should work.

# this is a wrapper to setTimeout
# it's used as a substitute to Datum.findOne in the question
delay = (ms, func) -> setTimeout func, ms

myRange = 4

myWrapper = (offset, range) ->             # range could be queryDate
  delay 0, ->                              # delay could be Datum.findOne
    console.log offset + ' ' + range

for offset in [1..myRange]
  myWrapper offset, myRange

delay is an alias (kind of) to setTimeout as suggested here

Community
  • 1
  • 1
giulp
  • 2,200
  • 20
  • 26
  • Could you explain briefly the need for a timeout/delay? Removing the delay produces the same output and the program completes instantly. – mz3 Jul 09 '14 at 14:34
  • delay (setTimeout) was just a substitute for Datum.findOne I put there to test a simple function with callback, no need to delay actually, sorry if it was confusing – giulp Jul 10 '14 at 21:30