Question

Suppose I have array of objects, each have some asynchronous readValue function, which accepts callback as its parameter, which will be fired when value is available for this object.

My goal is to calculate all values of each object and return array with those values.

First that came into my head is this

calculateValues = (sources, callback) ->
    counter = 0
    length = sources.length
    result = []

    for source in sources
        source.readValue (value) ->
            result.push value
            counter++
            callback result if counter is length

Since readValue method is asynchronous then function which calls it is also asynchronous. When all values will be in result array, callback function will be executed.

But all this seems messy to me. This is the area where coffeescript is really powerful. Is it possible to write this function even with less code with comprehensions? That's OK if it will be synchronous.

Was it helpful?

Solution

Unfortunately, CoffeeScript won't help you that much with these kinds of problems involving asynchronous code (apart from having a more terse function syntax, which is quite nice actually).

Your code works, but with one small caveat: the order of the values in the results array does not match with the order of their sources in the sources. Here's a small working snippet that uses setTimeout and a random number to get the desired unpredictable asynchronous behaviour:

calculateValues = (sources, callback) ->
  counter = 0
  length = sources.length
  result = []

  for source in sources
    source.readValue (value) ->
      result.push value
      counter++
      callback result if counter is length

sources = for i in [1..5]
  do (i) ->
    readValue: (cb) -> 
      setTimeout (-> cb "Value #{i}"), Math.random() * 1000

calculateValues sources, (results) -> 
  console.log "results", results

jsFiddle

Sample output:

results ["Value 2", "Value 5", "Value 1", "Value 3", "Value 4"]

If you wanted to preserve the ordering, it's a pretty simple fix, but this showcases how tricky it can be to get async code right.

Fortunately, there are better ways to express this kind of construct with the help of other tools. I'd recommend using a simple library like Async.js to help with synchronizing things. Here's the same snippet rewritten to use async.parallel and Node's convention of using two-argument callbacks of the form function(err, value) (and also a bit of Underscore.js little help, which you can translate to native CS very easily if you don't want it):

calculateValues = (sources, callback) ->
  funcs = _.pluck sources, 'readValue'
  async.parallel funcs, callback

sources = for i in [1..5]
  do (i) ->
    readValue: (cb) -> 
      setTimeout (-> cb null, "Value #{i}"), Math.random() * 1000

calculateValues sources, (err, results) -> 
  console.log "results", results

jsFiddle

Output:

results ["Value 1", "Value 2", "Value 3", "Value 4", "Value 5"]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top