Question

I am attempting to fix up some third party NodeJS (hubot-scripts if anyone cares). The idea here is that we reach out to the Teamcity REST API once for every build returned from a previous call and iterate over them, printing a message.

mapBuildTypeIdToName = (msg, id, callback) ->
    url = "http://#{hostname}/httpAuth/app/rest/buildTypes/id:#{id}"
    msg.http(url)
      .headers(getAuthHeader())
      .get() (err, res, body) ->
        err = body unless res.statusCode = 200
        buildName = JSON.parse(body).name unless err
        callback err, msg, buildName

createAndPublishBuildMap = (builds, msg) ->

    for build in builds
      console.log "foo"
      console.log build
      mapBuildTypeIdToName msg, build['buildTypeId'], (err, msg, name)->
        console.log "bar"
        console.log build
        baseMessage = "##{build.number} of #{name} #{build.webUrl}"
        if build.running
          status = if build.status == "SUCCESS" then "**Winning**" else "__FAILING__"
          message = "#{status} #{build.percentageComplete}% Complete :: #{baseMessage}"
        else
          status = if build.status == "SUCCESS" then "OK!" else "__FAILED__"
          message = "#{status} :: #{baseMessage}"
        msg.send message

The problem is as follows: at the start of the for loop, builds has multiple objects. These get iterated over and then the callback is entered, thus printing out the last build builds.length times.

The server log looks like so:

     foo
     build1
     foo
     build2
     bar
     build2
     bar
     build2

While expected/desired behaviour is:

    foo
    build1
    bar
    build1
    foo
    build2
    bar
    build2

So I have two questions:

1) Why is this behaviour occurring? I understand that the non blocking nature of Node JS is causing the primary thread (for loop) to execute faster then its non blocking child thread (mapBuildTypeIdToName). Why doesn't it still call the nested function with the right values? Why does it only get called with the last value?

2) How do I fix this? How can I block a parent thread and wait for a callback, or else, what is the canonical approach to problems of this nature (effectively an event driven busy loop) in Node JS?

Was it helpful?

Solution

I assume build is a string. If so, it will be the value you are expecting inside mapBuildTypeIdToName, as it is a primitive and they are passed by value.

Inside your callback, however, you are referencing the build variable declared outside the loop, so it's state will be whatever state that variable is in when the callback gets called. In your case, the callback is getting called after the loop has finished, so it is build2.

To get the results you are looking for, take a look at the eachSeries API in the async library. It will allow you to run a series of asynchronous API calls on an array as if they were synchronous, without actually blocking the thread.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top