I wanted to create a function in JavaScript using jQuery's Deferred which executes a given function for each element in an array.
The function is intended for operations which take approximately the same amount of time for any element. It is also only intended for items which need to be processed through the same function (i.e. only a single one).
The provided function must be executed for each array item. These executions do not have to return results but must succeed (i.e. stop) in some way.
Please help me stating problems in my new code and improving it.
/**
* Executes a function {func} for each item in the array {arr} with the inital amount of parallel calculations as specified in {par}
* @method runAsyncXtimesParallel
* @param {Array} arr (required) - the array containing the items to be processed
* @param {Function} func (required) - the function which should be executed for each array element
* @param {Number} parallel (required) - the amount of initial parallel executions
* @param {Boolean} taskdebug (optional) - indicates if the "task #x finished" message should be written into console
* @param {Boolean} percentagedebug (optional) - indicates if the current percentage progress should be written into console
* @return {Object} masterdeferred - a jQuery Deferred representing the function
*/
function runAsyncXtimesParallel(arr, func, par, taskdebug, percentagedebug)
{
//type checks
if (!(arr instanceof Array)) { throw new TypeError('first parameter is expected to be an array.'); }
if (!(func instanceof Function)) { throw new TypeError('second parameter is expected to be a function.'); }
if (typeof par !== "number") { throw new TypeError('third parameter is expected to be a number.'); }
if (typeof (taskdebug || true) !== "boolean") { throw new TypeError('fourth parameter is expected to be a boolean.'); }
if (typeof (percentagedebug || true) !== "boolean") { throw new TypeError('fifth parameter is expected to be a boolean.'); }
//change par to a non floating point number
par = parseInt(par);
//create the master deferred for the function
var masterdeferred = new $.Deferred();
//start promising the deferred
masterdeferred.promise();
//array for saving the task deferreds
var deferreds = [];
//array zum speichern der ergebnisse
var results = undefined;
//variable for storing the current processed count
var processedcount = 0;
//variables for storing the current progress percentage
var percentage = 0;
var tmppercentage = 0;
//loop through the first array items as long as there are some left and it does not exceed the given parallel count
for (var i = 0; i < par && i < arr.length; i++)
{
//create a new sub-deferred for each parallel task
var def = new $.Deferred();
//promise the deferred
def.promise();
//save the background data for the deferred item
def.options = {
//property containing the current array item
args: arr[i],
//property containing the deferred itself
deferred: def,
//the index of the deferred (i.e. time of creation and index in deferreds array
deferredindex: 0 + i,
//the current array item index
parindex: 0 + i,
//the function to be executed
work: func,
//function to check if
next: function(result) {
//can only proceed if this is set
if (this === undefined) return;
//check if a result was provided at function call, if so add it to the results array
if (result !== undefined) { results = results || []; results.push(result); }
//check and show the current percentage progress of all items if the user wants to see it
if (percentagedebug === true)
{
//increase the processed counter
processedcount++;
//temporarily calculate the progress in percent
tmppercentage = Math.floor(processedcount * 100 / arr.length);
//check if the progess increased since the last calculation
if (tmppercentage > percentage)
{
//output the current progress to the console
console.log("state: still working, currently at " + tmppercentage + " percent");
}
//recalculate and store the current progress
percentage = Math.floor(processedcount * 100 / arr.length);
}
//check if there are still items left to process
if (this.parindex + par < arr.length)
{
//increase the current processed item index with the parallelcount
this.parindex += par;
//get the new item to be processed
this.args = arr[this.parindex];
//process the new item
this.work(this.args, this);
}
else
{
//check and show if the user wants to see when the single task has finished
if (taskdebug === true) {console.log("task #" + this.deferredindex + " finished!"); }
//no more items for this "task" to process thus resolve it
this.deferred.resolve();
}
}
};
//push the deferred into the array
deferreds.push(def);
//start working at the deferred
def.options.work(def.options.args, def.options);
}
//use jQuery to wait until all sub-deferred get resolved
$.when.apply($, deferreds).then(function() {
//save the result to the deferred object
masterdeferred.results = results;
//resolve the master deferred with the results
masterdeferred.resolve(results);
});
//return the master deferred
return masterdeferred;
}
This is how an example array will look:
//create an array to store the user items
var workitems = [];
//for the users 0 to 9999
for (var i = 0; i < 10000; i ++)
{
//create a new object representing the user, the list and some rights
workitems.push({
list: "exampleList",
user: "user." + i,
rights: [ 1, 2, 3 ]
});
}
The executing function will look like this:
function myFunc(workingitem, options) {
//issue an ajax post to the server
$.ajax({
type: "POST",
url: "https://example.com/upload.php",
data: workingitem,
success: function(text) {
//call succeeded, continue execution
options.next({ status: "ok", data: text });
},
error: function(xhr, text, error) {
//call failed but continue execution
options.next({ status: "error", data: text });
}
});
}
The main call by myself will be in the following way:
//run the function for each array item with 10 parallel executions
var raxp = runAsyncXtimesParallel(workitems, myFunc, 10);
//wait until the function processed all array items
$.when(raxp).done(function(results) {
//results are available in the {results} argument and as a property of the master deferred
//work with the result
doFurtherProcessing(raxp.results);
});
thisinstead of def. Thanks for pointing out. Please feel free to try again :) \$\endgroup\$