Asynchronous Processes Inside of a Loop in JavaScript

As the web development world becomes more and more asynchronous with the emergence of technology stacks like Node.js, REST APIs, and some of the features we see in ECMAScript 6 (the latest version of JavaScript), there are whole new dimensions involved with how we exchange data over the web and how we code for this. One of the challenges with asynchronous programming is that there might be some additional considerations regarding the specific time that certain parts of your code executes and/or the order in which data is coming in to your callback functions that you set up to execute when your processes are complete. If things happen when you don’t expect them or in a different order than you expect them if you’re not ready to handle this, you’re going to get some undesirable results.

What is an example of this? What if, say, I had to do something this?

for(var i=1; i < 6; i++) {
    asyncFunction(function(data){
        console.log('The index is ' + i);
        console.log('The data is ' + data);
    });
}

Here I have set up a callback that will execute when our hypothetical asynchronous process is complete, but I also want to have access to the value of the index in the callback. If I had a list of items and I wanted to do an async process on each of the items in my list, this is some information that I would want.

If were to run this we might see something like the following in our console…

The index is 5
{ data: "item2" }
The index is 5
{ data: "item1" }
The index is 5
{ data: "item3" }
The index is 5
{ data: "item5" }
The index is 5
{ data: "item4" }

Hmm, that doesn’t look right. Why is this happening? Well, it’s because as far has JavaScript is concerned the asynchronous process that we run is done executing and JavaScript moves back up to the top of the loop and continues on. Thus, the value of the index in our loop rockets to 5 before any data comes back. What’s more, the data that we are getting back is coming in at different times in no particular order.

If we wanted to do things in a particular order, this state of things might be difficult. We might end up having to do some sorting after we get our data back for each item and we really do not have any idea when we are actually done (apart from maybe keeping a count of the response we get from our async process). This is a cumbersome and non-performant way of doing things. What we need is a way to maintain the context of the index of our loop in our callback function(s) so that we are able to keep some semblance of order as we do these async processes. How can we accomplish this?

The answer to this is fairly straightforward: function factories. That is, a function that returns another function. You might be familiar with the concept of a function factory if you have ever looked at the factory pattern in your study of software development. These functions that we return will be created on the fly and as a result of this they will have their own context when they are created. In essence, our function that creates these functions becomes our “callback factory” … a function that makes callbacks to run when we get our data back.

Let’s take a look at a practical example of this. In this example I am going to use a demo API that returns some sample data in JSON format. I am going to use jQuery to fetch this data via the ajax method (AJAX being an acronym for Asynchronous JavaScript and XML). Getting this data is, of course, going to be an asynchronous process. In the success method we will make a call to our function factory which will create a new function for us. This function will maintain its context at the time that it is created (and thus will have the correct index value of our loop at the time it is created). This will look like something like the following…

function callbackFactory(i){
    return function(data, textStatus, jqXHR){
        console.log('We are calling with index ' + i + ' and the data is...')
        console.log(data);
    }
}

for(var i=1; i < 6; i++) {

    jQuery.ajax({
        type: "GET",
        url: "http://jsonplaceholder.typicode.com/posts/" + i,
        contentType: "application/json; charset=utf-8",
        async: true,
        dataType: "json",
        success: callbackFactory(i),
        error: function (jqXHR, textStatus, errorThrown) {
            console.log(errorThrown);
        }
    });
}

So if we run this code, we can see that the index value is preserved. And we get the values logged to the console like so…

We are calling with index 5 and the data is...
{userId: 1, id: 5, title: "nesciunt quas odio", body: "repudiandae veniam quaerat sunt sed↵alias aut fugi...sse voluptatibus quis↵est aut tenetur dolor neque"}
We are calling with index 2 and the data is...
{userId: 1, id: 2, title: "qui est esse", body: "est rerum tempore vitae↵sequi sint nihil reprehend...aperiam non debitis possimus qui neque nisi nulla"}
We are calling with index 1 and the data is...
{userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit↵suscipit recusandae consequuntur ...strum rerum est autem sunt rem eveniet architecto"}
We are calling with index 4 and the data is...
{userId: 1, id: 4, title: "eum et est occaecati", body: "ullam et saepe reiciendis voluptatem adipisci↵sit ... ipsam iure↵quis sunt voluptatem rerum illo velit"}
We are calling with index 3 and the data is...
{userId: 1, id: 3, title: "ea molestias quasi exercitationem repellat qui ipsa sit aut", body: "et iusto sed quo iure↵voluptatem occaecati omnis e...↵molestiae porro eius odio et labore et velit aut"}

Although we do not have control over when our async processes are complete (our data still comes back at different times in no particular order),we at least still have a reference as to what item in our loop corresponds to which set of data we get back from our AJAX calls (which is far better than what we had previously).

What we looked at above is just a simple example of how to conceptualize some of the considerations involved with asynchronous programming in JavaScript. Beyond this, there are certainly more structured implementations like using promises. A promise object is used for deferred and asynchronous computations and it represents an operation that hasn’t completed yet, but is expected in the future. An example of a promise implementation using jQuery and our API would look something like the following…

jQuery(document).ready(function() {  
    
    function getPost(id){
        var def = jQuery.Deferred();

        jQuery.ajax({ 
            url: "http://jsonplaceholder.typicode.com/posts/" + id,
            type: "GET",
            dataType: "json",
            success: function(data){
                def.resolve(data)
            },
            error: function(){
                def.reject();
            }
        }); 

        return def.promise();
    }

   
    getPost(1).then(function(data){
        console.log(data);
        return getPost(2); 

    }).then(function(data){
        console.log(data);
        return getPost(3); 

    }).then(function(data){
        console.log(data);

    }).fail(function(error){
        console.log('Sorry an error ocurred!')
        def.reject();
    });   
    
});

See here we have a function called “getPost” that will return a promise for us that the jQuery library provides. We can then use that function to essentially queue up the requests we need to make so that we can get the data back in the order that we need it. If you are not familiar with promises it is very worthwhile to learn about them because they are fairly prevalent in modern JavaScript (at least as of circa midway through the 2010s decade). There are lots of promise libraries and popular libraries and frameworks like jQuery and Angular have some sort of promise pattern built into them.

Apart from promises there are also other methods of handling asynchronousity and what you decide to implement really depends on the problem(s) you are trying to solve, how and when you need to get your data back and all of that. Like with many things, there is no magic bullet solution that will work in all instances across all codebases. It really comes down to what is going to work best for your particular implementation. Hopefully, however, this introduction has been a good introduction into *how* you think about asynchronous operations. If you can conceptualize how things are occurring, you can determine the best approach to structuring your code.

9bit Studios E-Books

Like this post? How about a share?

Stay Updated with the 9bit Studios Newsletter

0 Responses to Asynchronous Processes Inside of a Loop in JavaScript

Leave a Reply