mainsocial
mainsocial

Reputation: 7823

asynchronous map in coffeescript

Is there an elegant way to asynchronously map an object or array in coffeescript? (Or javascript.)

Imagine I have some things in an object:

things = 
  x: 
    ...
  y:
    ...
  z:
    ...

thingCount = 3

I want to create a method that will process each of these things and return the processed object. The process has to make an asynchronous call to fetch some info about each thing. At first I tried just to loop through the properties like so:

processThings = (callback) ->

  processedThings = {}
  count = 0

  for key,val in things

    asyncJob key,val (err,result) ->

      if err
        callback error
      else
        # PROBLEM: key has the incorrect value here
        processedThings[key] = result      
        count += 1
        if count == thingCount
          callback null,processedThings

The problem is that the value of key changes in the loop. So my solution is to create a sub-function so that the key variable is contained within its closure:

processThings = (callback) ->

  processedThings = {}
  count = 0

  processThing = (key,val) ->

    asyncJob key,val (err,result) ->

      if err
        callback error
      else
        processedThings[key] = result      
        count += 1
        if count == thingCount
          callback null,processedThings

  processThing key,val for key,val of things

But boy howdy that sure is fugly. Is there a preferred pattern for this?

Upvotes: 2

Views: 811

Answers (1)

Jonathan Lonowski
Jonathan Lonowski

Reputation: 123513

CoffeeScript covers this with the do keyword, described at the end of Loops and Comprehensions:

When using a JavaScript loop to generate functions, it's common to insert a closure wrapper in order to ensure that loop variables are closed over, and all the generated functions don't just share the final values. CoffeeScript provides the do keyword, which immediately invokes a passed function, forwarding any arguments.

for filename in list
  do (filename) ->
    fs.readFile filename, (err, contents) ->
      compile filename, contents.toString()

It can be applied to your code as:

processThings = (callback) ->

  processedThings = {}
  count = 0

  for key,val in things

    # add this to close over `key`
    do (key) ->

      asyncJob key,val (err,result) ->

        if err
          callback error
        else
          processedThings[key] = result      
          count += 1
          if count == thingCount
            callback null,processedThings

Upvotes: 5

Related Questions