doing a while loop in node.js, always give me the last array item?

im using the zombie.js library , to do some testing for the dom, i have this script

Browser = require "zombie"
arr = new Array("http://yahoo.com", "http://google.com", "http://msn.com")
i = 0
while i < arr.length
    b = new Browser()
    b.visit arr[i], ->
        console.log b.text "title"
    b.close()
    i++

everything works, but it only logs the msn title two times, so the while loop is logging the last item in the array twice. i can't seem to see what the problem is?

You're registering a callback for when the browser's visit has completed. By that time, the loop has run to completion, so the variable b points to the last browser you created, which means its title will be that of MSN, not those of the other pages. In order to fix this, use a closure:

Browser = require "zombie"
arr = new Array("http://yahoo.com", "http://google.com", "http://msn.com")
i = 0
createBrowser = (url) ->
    b = new Browser()
    b.visit url, -> console.log b.text "title"
    b.close()

while i < arr.length
    createBrowser(arr[i++])

This works because now there is a separate scope for each browser you create, and a separate browser variable.

Alternatively, use the callback arguments for ZombieJS's visit function:

Browser = require "zombie"
arr = new Array("http://yahoo.com", "http://google.com", "http://msn.com")
i = 0
while i < arr.length
    b = new Browser()
    b.visit arr[i], (e, myBrowser) ->
        console.log myBrowser.text "title"
    b.close()
    i++

The visit method is asynchronous, taking a callback function. In the other words, visit is called three times before any logging happens, i.e. console.log is not executed between b.visit and b.close calls. By the time the last callback finishes, b refers to "last" Browser instance (the msn one). The visit method likely passes the result for the callback, please check the documentation.

Browser = require 'zombie'

for url in ["foo", "bar", "baz"] # elegant way to loop lists in coffeescript
  b = new Browser
  b.visit url, (err, b) ->       # b given as argument masks the b in outer scope
    console.log b.text "title"
    b.close()

I think what you want to do is something like the following (warning, untested!):

Browser = require "zombie"
sites = ['http://yahoo.com', 'http://google.com', 'http://msn.com']

process = (site) ->
  b = new Browser()
  b.visit site, () ->
    title = b.text title
    console.log title
    b.close()

process site for site in sites

I've taken the liberty of changing a few bits and pieces - hopefully it's still clear.

JavaScript (and CoffeeScript) have strange scoping rules if you aren't used to them. I find it's usually best to do all work in a function to make things work as you expect.

I don't know exactly how zombie.js works, but I think this should do as you expect.