IFFEs used for DOM manipulation, asynchronous functions and ultimate power

The following code will attach three labeled buttons to the DOM. The buttons have callbacks that should alert ‘first’, ‘second’ and ‘third’.

   var result = [‘first’, ‘second’, ‘third’];
for (var i = 0; i < result.length; i++) {
var button = document.createElement(‘button’);
var text = document.createTextNode(‘button ‘+ i);
button.appendChild(text)
var body = document.querySelector(‘body’)
body.appendChild(button)
button.onclick = function callback(i) {
// what is the result?
alert(result[i]);
};
}

However, we can see that the result is undefined.

Once the callback attached to the button gets executed the variable lookup will traverse the scope chain and find a match in the global scope. The for loop has already incremented the variable literal i to 3 in the global scope. Accessing the non existent fourth element in results array yields undefined.

How can we fix this? We can retain a unique variable in the closure scope of an immediately invoked function (IIFE or ‘iffy’). This gives us a closure where the value of i can be retrieved during the variable lookup.

Here is an example of saving a value in closure scope with an IIFE.

var i = 2
var IIFE = function (i){
return function (){
console.log(i);
}
}(i)// immediately executing the IIFE function with i as a parameter
i=3
IIFE()
// logs '2'

Notice how the the function outputs 2 despite the value of i being changed to 3 in the outer scope. Lets use an IIFE to fix the first button example.

 var result = [‘first’, ‘second’, ‘third’];
for (var i = 0; i < result.length; i++) {
var button = document.createElement(‘button’);
var text = document.createTextNode(‘button ‘+ i);
button.appendChild(text);
var body = document.querySelector(‘body’);
body.appendChild(button);
button.onclick = function IIFE(i) {
return function inner(){
alert(result[i]);
}
}(i);

We can see that buttons are alerting us to the correct values ‘first’, ‘second’ and ‘third’ .

Lets use the power of IIFEs to build an async map function. It will take asynchronous functions, store their results in order and apply a callback ont the results array once all the functions have been resolved.

Here is my implementation of async map , notice the power of the IIFE , which maintains the value of i specific to each of the async functions.


function asyncMap(tasks,callback){
// turn argument object into an array
var task, counter, results, i
counter = tasks.length
results = []
for (i = 0; i < tasks.length; i++) {
(function IIFE(i){
task = tasks[i];
task(function(data){
results[i] = data
counter -= 1;
if (counter === 0 ){
callback(results)
}
})
})(i)
};
}

Here is an example of asyncMap being used with setTimeout functions.

asyncMap([
function(cb){
setTimeout(function(){
cb(‘one’);
}, 1000);
},
function(cb){
setTimeout(function(){
cb(‘two’);
}, 500);
},
function(cb){
setTimeout(function(){
cb(‘three’);
}, 300);
}
],
function(results){
// the results array will be [‘one’,’two’,'three'] even though
// the second and third function had a shorter timeout.
console.log(results); // ['one' , 'two' ,'three']
})