Batch Function

I introduced another pattern into the Event package and wanted to explain its mechanics, before introducing it. I attached this to my Core package (Core.batch).

A batch Function is a powerful tool used for replacing/simplifying for loops. Anytime you are iterating on each element in an Array or Array-like structure, then you can use a batch function in its stead. A batch function will perform the iteration for you and pass the value of the element at index, the index, and any additional parameters into the function you provide it. Here is my current batch Function:

/**
 * Executes fn on each element in the set
 *
 * @method batch
 * @param set {array} the collection to iterate on
 * @param fn {function} the function to execute on each member of the collection; iterator function
 * @param {arguments} any number of arguments to pass into fn
 * @public
 */
batch: function(set, fn) {
	if (! set.length) {return;}
	var args = Array.prototype.slice.apply(arguments, [2]);
	args.unshift(null, null);

	// iterate on the items, executing the function, and stoping when function returns true or touched all childnodes
	for (var i = 0, j = set.length; i < j; i++) {
		args[0] = set[i];
		args[1] = i;
		if (fn.apply(this, args)) {
			return;
		}
	}
},

The first line ensures that the set is iterable. If it is not, then we return undefined. Next we leverage the slice method of the Array prototype on the arguments, which always returns an array, so any array-like Object (nodelists, arguments, etc.) becomes an array. We trim out the first two values of the arguments, which are the parameters: "set" and "fn", and assign the result to args. Two empty elements are pushed into the front of the args. As we iterate through the set, we put values into the first two members of args (0: value at index, 1: index). All other parameters passed into batch, those after set and fn, will also be passed into the iterator function as parameters using the apply method. The last part is to test the results of the iterator function and stop the batch process anytime it returns true. This way you do not always need to iterate on every member of the array.

Now, to see batch in action, suppose you have a attachEventToSet. This Function iterates through an Array and calls attachEvent on each member of the Array.

attachEventToSet: function(set, eType, fn, capture) {
	for (var i = 0, j = set.length; i < j; i++) {
		Event.attachEvent(set[i], eType, fn, capture);
	}
},

this could be simplified to:

attachEventToSet: function(set, eType, fn, capture) {
	Core.batch(function(el, i) {
		Event.attachEvent(el, eType, fn, capture);
	});
},

The definition of the inner Function inside of the attachEventToSet Function closure, provides access to the parameters: eType, fn, and capture. It you so choose, you could have also written the batch, as follows:

attachEventToSet: function(set, eType, fn, capture) {
	Core.batch(function(el, i, eType, fn, capture) {
		Event.attachEvent(el, eType, fn, capture);
	}, eType, fn, capture);
},

Here, you are passing extra parameters through the batch Function and assigning them directly to the inner Function, instead of relying on the closure. Whenever possible, I usually use closures as the code is more concise.

Some possible areas of improvement are:

  • scope management - it may be useful execute the inner Function in a particular scope; I havent had a need to yet
  • validation - validate array and value of the array, also throw exceptions (or other error management) when bad parameters have been passed
  • quick functions - a small collection of common batch functions, such as comparison and update methods