Node.js – How to Write a For Loop With Callbacks

Let’s say you have 10 files that you need to upload to your web server. 10 very large files. You need to write an upload script because it needs to be an automated process that happens every day.

You’ve decided you’re going to use Node.js for the script because, hey, it’s cool.

Let’s also say you have a magical upload function that can do the upload:

upload('myfile.ext', function(err){
  if( err ) {
    console.log('yeah, that upload didn't work: '+err)
  }
})

This upload function uses the callback pattern you’d expect from Node. You give it the name of the file you want to upload and then it goes off and does its thing. After while, when it is finished, it calls you back, by calling your callback function. It passes in one argument, an err object. The Node convention is that the first parameter to a callback is an object that describes any errors that happened. If this object is null, then everything was OK. If not, then the object contains a description of the error. This could be a string, or a more complex object.

I’ll write a post on that innards of that upload function – coming soon!

Right, now that you have your magical upload function, let’s get back to writing a for loop.

Are you a refugee from Javaland? Here’s the way you were thinking of doing it:

var filenames = [...]

try {
  for( var i = 0; i < filenames.length; i++ ) {
    upload( filenames[i], function(err) {
      if( err ) throw err
    })
  }
}
catch( err ) {
  console.log('error: '+err)
} 

Here's what you think will happen:
1. upload each file in turn, one after the other
2. if there's an error, halt the entire process, and throw it to the calling code

Here's what you just did:
1. Started shoving all 10 files at your web server all at once
2. If there is an error, good luck catching it outside that for loop – it's gone to the great Event Loop in the sky

Node is asynchronous. The upload function will return before it even starts the upload. It will return back to your for loop. And your for loop will move on to the next file. And the next one.

Is your website a little unresponsive? How about your net connection? Things might be a little slow when you push all those files up at the same time.

So you can't use for loops any more! What's a coder to do? Bite the bullet and recurse. It's the only way to get back to what you actually want to do.

You have to wait for the callback. When it is called, only then do you move on to the next file. That means you need to call another function inside your callback. And this function needs to start uploading the next file. So you need to create a recursive function that does this.

It turns out there's a nice little recursive pattern that you can use for this particular case:

var filenames = [...]

function uploader(i) {
  if( i < filenames.length ) {
    upload( filenames[i], function(err) {
      if( err ) {
        console.log('error: '+err)
      }
      else {
        uploader(i+1)
      }
    })
  }
}
uploader(0)

Do you see the pattern?

repeater(i) {
  if( i < length ) {
     asyncwork( function(){
       repeater( i + 1 )
     })
  }
}
repeater(0)

You can translate this back into a traditional for(var i = 0; i < length; i++) loop quite easily:

repeater(0) is var i = 0,
if( i < length ) is i < length, and
repeater( i + 1 ) is i++

When it comes to Node, the traditional way of doing things can mean you lose control of your code. Use recursion to get control back.

This entry was posted in Node.js. Bookmark the permalink.

4 Responses to Node.js – How to Write a For Loop With Callbacks

  1. Narendra says:

    Nice article… I could not understand the final translating to traditional for loop part though…

  2. James Coglan says:

    Clear explanation. Worth bearing in mind the edge case when you’re doing work that *might* be asynchronous without causing a stack overflow: http://blog.jcoglan.com/2010/08/30/the-potentially-asynchronous-loop/

  3. Dhruv Matani says:

    Actually, you can set maxSockets to 1 and subsequent requests will be queued up. I would prefer using a queue in this case and use a for loop instead of recursion. However, the recursion trick is an interesting one!! Another place the for loop will fail you is if you try to use the value of ‘i’ in the callback for upload()

  4. Nice article.. I also quite like the async module, very flexible on flow control: https://github.com/caolan/async

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>