Fabricator Fabricator - 6 months ago 21
Ajax Question

Randomize promises that uses ajax

I've constructed a demo that randomizes promises. It build an array of promises that would resolve 1 to 100. Then shuffle this array, and get results. It works, and we can see it outputs in an random order. (fiddle)



var promises = [];
for (var i = 0; i < 100; i++) {
promises.push(new Promise(function(resolve, reject) {
resolve(i);
}));
}

// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
function shuffle(array) {
var currentIndex = array.length,
temporaryValue, randomIndex;

// While there remain elements to shuffle...
while (0 !== currentIndex) {

// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;

// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}

return array;
}

shuffle(promises).forEach(function(r) {
r.then(function(v) {
$('#out').append('<div>'+v+'</div>')
})
});

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="out">
</div>





However, when I incorporate ajax in the promise, the result is no longer random. The output is sometimes out of order, but mostly in ascending order. The part that is different from the previous demo is in building the array of promises. fiddle

var promises = [];
for (var i = 0; i < 100; i++) {
promises.push(new Promise(function(resolve, reject) {
$.ajax({
url: '/echo/json/',
data: { json: JSON.stringify({"v": i}) },
method: 'POST',
success: function(rs) {
resolve(rs.v);
}
});
}));
}


Why?

Answer

Promises are eager, this means they run as soon as we define them.

In the first example all the promises are actually resolved as soon as they are defined, because there is no asynchronous expression inside. In the second loop (the forEach), each promise gets a then handler and the data is added to the document in the same order that the promises appear on the promises array.

In the second example it's a bit different, you actually define an asynchronous (ajax) task. Probably the interpreter gets to the seconds loop (the shuffle(promises).forEach) before any of the promises are resolved. This means the then handlers are assigned in a random order BUT now all the promises are waiting to be resolved! And they resolve one by one, by the original order. The first network request will be resolve first, then the second one and so on.

To further explain this situation, let wrap the forEach loop in a setTimeout:

setTimeout(function() {
    shuffle(promises).forEach(function(r) {
        r.then(function(v) {
            $('#out').append('<div>'+v+'</div>')
        });
    });
}, 5000);

Now, the forEach loop will run after all the promises are resolved (assuming 5 seconds is enough for the 100 ajax calls) and the numbers will be displayed in a random manner as in the first example.