0

I have an ids object, which maps id strings to product objects.

for id of ids
  product = ids[id]
  console.log product # Prints out something different each loop. :)
  Product.create(product).then ->
    console.log product # Only prints out the last id each loop. :(

I'm using a library for database interactions, which exposes promises (indicated by the then function above). I'm trying to print out the product variable inside the then function, but I only seem to be getting the last id in ids, so it looks like it's a scoping issue. How can I scope the product variable properly so that it prints out a different product in the then function each loop?

XåpplI'-I0llwlg'I -
  • 21,649
  • 28
  • 102
  • 151

2 Answers2

2

@false did find the right duplicate describing your issue. Indeed, you've got a scoping issue where product is non-local to the loop body, and you get the last item only from your asynchronous callbacks.

How can I scope the product variable properly so that it prints out a different product in the then callback?

In idiomatic coffeescript, you will use the do notation for the IEFE in the loop:

for id of ids
  do (product = ids[id]) ->
    console.log product
    Product.create(product).then ->
      console.log product

Or, drawing the property value directly from the of-loop:

for id, product of ids
  do (product) ->
    …
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

Bergi's code is misleading IMO since it runs the whole loop at once, not sequentially. For that reason I would just lift all the code to work in promises instead of mixing sync and async:

Promise.resolve(product for _, product of ids).then next = (products) ->
  [product, products...] = products
  if product
    console.log product
    Product.create(product).then ->
      console.log product
      next products
.then ->
  console.log "all done"

The difference is:

  • Like in a real loop, next item won't run until the previous has completed
  • Like in a real loop, the next line (just needs a then -> runs only after the loop has completed completely

These properties of a real loop are much more important than superficial syntax which you can learn in a couple of days.

Let it run and look at the difference in the logs.

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • I don't think OP wanted to execute them sequentially. You might of course easily run them in parallel and collect their results by `Promise.map((p for _, p of ids), Product.create)` or similar. My answer actually wasn't about the promise part, only about the closure. – Bergi Jun 04 '14 at 08:36
  • 2
    @Bergi I don't think he is even aware of such difference (sequential vs parallel), I am here to enlighten – Esailija Jun 04 '14 at 09:01