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?