turn array into function, making a javascript loader with jquery - javascript

how can we make a function from arrays like
$.loader({
js: [
['1.js','3.js','2.js'],
['4.js'],
['5.js']
]
});
into something that does
$.when(
$.getScript("1.js"),
$.getScript('3.js'),
$.getScript('2.js'))
.then(
function(){
$.getScript('4.js').then(function(){
$.getScript('5.js');
})
});
heres what currently im working on
loader: function(arg){
var js = arg;
var phase = 0;
for (var i = 0; i < o.length; i++) {
console.log(o[i]);
$.each(o[i], function(key,src) {
//append javascript
});
};
}
the problem is i dont know where to start.
heres what i have in mind
i divide them into phases.
set phase 0, it will run the fist array but how can i do it ? $.getScript(src) for each src dosent stack. or chain. maybe put it one var sting ? like string + = $.getScript(src). so it will be like $.getScript("1.js").getScript("3.js").getScript("2.js") until there is arrays of string which is 3 in this example. then do something like append $.when( infront of the string then at the back of the string we put .then(function(){ until it finish then somehow closed the string.
ok i dont know ( and here goes the post at stackoverflow )

function lScripts(startAt){
for (var i = startAt; i < js.length; i++) {
if (js[i].constructor == Array){
for (var j = 0; j < js[i].length; j++) {
if(j==js[i].length){
$.getScript('js[i][j]',function(){
lScripts(startAt+1);
});
}else{
$.getScript('js[i][j]');
}
}
}else{
$.getScript('js[i]',function(){
lScripts(startAt+1);
});
}
}
}
lScripts(0);

Just to be clear, are you talking about writing a solution yourself that loads up a bunch of scripts? If so, dont do it yourself. Stand on someone else's shoulders.
RequireJS already does what you're asking.
http://requirejs.org/
It even has documentation for use with jQuery.
http://requirejs.org/docs/jquery.html
Someone else already did the hard work and wrote and tested a good product. Save yourself time and stress.

I hope this is what you mean: http://jsfiddle.net/pimvdb/hdN3Q/1/.
(function($) {
function load(files, index) {
var arr = [];
// fill arr with getScript requests (fake here)
for(var i = 0; i < files[index].length; i++) {
arr.push(console.log(files[index][i]));
}
// call $.when with arr as array of arguments (using apply)
$.when.apply(null, arr).then(function() {
if(index < files.length - 1) {
// if we have to move to the next chunk,
// load that next one
setTimeout(function() {
// timeout is to show they get processed in chunks
load(files, index + 1);
}, 1000);
}
});
}
$.loader = function(obj) {
load(obj.js, 0);
}
})(jQuery);
$.loader({
js: [
['1.js','3.js','2.js'],
['4.js'],
['5.js']
]
});
.apply essentially does the same thing as calling a function. However, because the amount of arguments to $.when differs depending on the input, you can't just call $.when(...) because you don't have a fixed number of arguments. The way to call a function with a variable amount of arguments is using .apply. It works like this:
someFunction(1, 2, 3);
is equal to:
someFunction.apply(null, [1, 2, 3]);
(The null refers to the execution context which is out of scope here.) The great thing about this is that you can build an array of any size. So you can call the function with any variable amount of arguments this way.
In the load function, arr gets filled with getScript functions, and it works the same way:
var arr = [getScript('file1'), getScript('file2')];
$.when.apply(null, arr);
is equal to:
$.when(getScript('file1'), getScript('file2'));
In load, we fill arr with the files of a chunk, e.g. in your case the first chunk is 1.js, 3.js and 2.js. This is done using a for loop but the array you will end up with you can just pass to .apply.
When all files are loaded, .then is called. The function we pass there should load the next chunk, but only if there is a next chunk. If there are 3 chunks, it should go to the next one when index is 0 or 1. When chunk 2 has finished, there is no next chunk so it should not continue to the next one as there isn't a next one.
So we need this if clause:
if(index < files.length - 1) {
Say files.length is 3. Then this if conditional will only pass when index is 0 or 1, which is what we want.

Related

Add elements to an already evaluated loop

Good afternoon, I encounter the following problem, I am trying to loop through a list with subdirectories to add to an array (repositories) the routes of these subdirectories, this is my code:
for (n=0; n<=pendingRepos.length; n++){
subruta = pendingRepos[pendingRepos.length -1]
pendingRepos.pop()
c.list(subruta, function(err, sublist) {
if (sublist.length != 0){
for (g=0; g < sublist.length; g++){
if (sublist[g].type === 'd' ){
repositories.push(subruta+'/'+sublist[g].name)
pendingRepos.push(subruta+'/'+sublist[g].name)
}
else {files.push(subruta+'/'+sublist[g].name)}
}
}
});
}
For example when starting the loop for my array pendingRepos has the following structure:
pendingRepos = ['/ dir1 / dir2', / dir3 / dir4 ']
the loop is executed correctly 2 times and the last element was removed, but at the time of the other loop to add another 'last' element to the array the first for loop does not take it into account.
I understand that the condition was already evaluated before I added more elements, is this correct? How can I avoid it?
It looks like you're treating the array of pending repos in two contradictory ways. The outer for loop:
for (n = 0; n <= pendingRepos.length; n++) { ... }
is treating pendingRepos as an immutable list, going through from beginning to end and processing each element. (And not doing this correctly, either - we should be iterating to n < pendingRepos.length if this is the option we're using).
The logic immediately after the loop, however,
subruta = pendingRepos[pendingRepos.length -1]
pendingRepos.pop()
treats pendingRepos as a mutable stack, from which you would keep processing the last element until the stack was empty.
In order to correctly process the array, you need to choose one or the other. Since it seems that the rest of your code is correctly using the stack approach, the loop at the top should be changed to match, which in this case would simply be
while (pendingRepos.length > 0) { ... }
The end result will look as follows:
while (pendingRepos.length > 0){
const subruta = pendingRepos[pendingRepos.length -1]
pendingRepos.pop()
c.list(subruta, function(err, sublist) {
if (sublist.length != 0){
for (let g = 0; g < sublist.length; g++){
if (sublist[g].type === 'd' ){
repositories.push(subruta+'/'+sublist[g].name)
pendingRepos.push(subruta+'/'+sublist[g].name)
} else {
files.push(subruta+'/'+sublist[g].name)
}
}
}
});
}
EDIT: The above answer only works if c.list() is a synchronous function that immediately runs your callback before returning - however, since it is contacting an FTP server, it is not. This means that the entire while loop will finish before any of those callbacks run, and anything they add to pendingRepos will not be processed. In order to use asynchronous functions, you have to structure your function completely differently, basically using more and more asynchronous functions as far up as you can go.
Fortunately, doing that is pretty easy in this case. What you are doing with pendingRepos is conceptually known as depth-first search (or "DFS"), where you search through a tree structure by repeating the search at each subnode. Using the stack of pending directories is one way to do DFS, and another way to do it is to use a recursive function (basically repeating the search function each time you reach a directory).
Here's a possible implementation of that, with the use of callbacks extending all the way out.
// an outer function for the whole operation. You would provide
// a callback that takes the lists of repositories and files.
function getTheRepos(startList, callbackForWholeThing) {
// build up our lists of repositories and files
const repositories = [];
const files = [];
// keep track of how many calculations are running
let repoGetCount = 0;
// an inner function to run exactly one result
function getOneRepo(subruta) {
// at the start, say we're running
repoGetCount++;
c.list(subruta, function(err, sublist) {
if (sublist.length != 0){
for (let g = 0; g < sublist.length; g++){
if (sublist[g].type === 'd' ){
repositories.push(subruta+'/'+sublist[g].name)
// for each directory we find, call this inner function again.
// This is the critical part that makes this all work.
getOneRepo(subruta+'/'+sublist[g].name)
} else {
files.push(subruta+'/'+sublist[g].name)
}
}
// at the end, say we're not running,
// and call the whole callback if we're the last one
repoGetCount--;
if (repoGetCount === 0) {
callbackForWholeThing(repositories, files);
}
}
});
// now that we have the function, run it on each of our
// start directories to start things off
for (let n = 0; n < startList.length; n++) {
getOneRepo(startList[n]);
}
// the cogs are in motion, so now return.
// The callback will be called when the tree has been searched.
}

RangError: too many arguments provided for a function call

I got a nice solution to get HTML Comments from the HTML Node Tree
var findComments = function(el) {
var arr = [];
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === 8) {
arr.push(node);
} else {
arr.push.apply(arr, findComments(node));
}
}
return arr;
};
var commentNodes = findComments(document);
// whatever you were going to do with the comment...
console.log(commentNodes[0].nodeValue);
from this thread.
Everything I did was adding this small loop to print out all the nodes.
var arr = [];
var findComments = function(el) {
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === 8) {
arr.push(node);
} else {
arr.push.apply(arr, findComments(node));
}
}
return arr;
};
var commentNodes = findComments(document);
//I added this
for (var counter = arr.length; counter > 0; counter--) {
console.log(commentNodes[counter].nodeValue);
}
I keep getting this Error Message:
RangeError: too many arguments provided for a function call debugger
eval code:9:13
EDIT: i had a typo while pasting changed the code from i-- to counter--
see this comment in MDN docs about the use of apply to merge arrays:
Do not use this method if the second array (moreVegs in the example) is very large, because the maximum number of parameters that one function can take is limited in practice. See apply() for more details.
the other note from apply page:
But beware: in using apply this way, you run the risk of exceeding the JavaScript engine's argument length limit. The consequences of applying a function with too many arguments (think more than tens of thousands of arguments) vary across engines (JavaScriptCore has hard-coded argument limit of 65536), because the limit (indeed even the nature of any excessively-large-stack behavior) is unspecified. Some engines will throw an exception. More perniciously, others will arbitrarily limit the number of arguments actually passed to the applied function. To illustrate this latter case: if such an engine had a limit of four arguments (actual limits are of course significantly higher), it would be as if the arguments 5, 6, 2, 3 had been passed to apply in the examples above, rather than the full array.
As the array start from index of 0, actually the last item in the array is arr.length - 1.
you can fix it by:
for (var counter = arr.length - 1; counter >= 0; counter--)
Notice I've added arr.length -1 and counter >= 0 as zero is the first index of the array.
Adding the for loop is not the only thing you changed (and see the other answer about fixing that loop too). You also moved the declaration of arr from inside the function to outside, making arr relatively global.
Because of that, each recursive call to findComments() works on the same array, and the .apply() call pushes the entire contents back onto the end of the array every time. After a while, its length exceeds the limit of the runtime.
The original function posted at the top of your question has arr declared inside the function. Each recursive call therefore has its own local array to work with. In a document with a lot of comment nodes, it could still get that Range Error however.

how to work with a large array in javascript [duplicate]

This question already has answers here:
Best way to iterate over an array without blocking the UI
(4 answers)
Closed 6 years ago.
In my application I have a very big array (arround 60k records). Using a for loop I am doing some operations on it as shown below.
var allPoints = [];
for (var i = 0, cLength = this._clusterData.length; i < cLength; i+=1) {
if (allPoints.indexOf(this._clusterData[i].attributes.PropertyAddress) == -1) {
allPoints.push(this._clusterData[i].attributes.PropertyAddress);
this._DistClusterData.push(this._clusterData[i])
}
}
When I run this loop the browser hangs as it is very big & in Firefox is shows popup saying "A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete". What can I do so that browser do not hang?
You need to return control back to the browser in order to keep it responsive. That means you need to use setTimeout to end your current processing and schedule it for resumption sometime later. E.g.:
function processData(i) {
var data = clusterData[i];
...
if (i < clusterData.length) {
setTimeout(processData, 0, i + 1);
}
}
processData(0);
This would be the simplest thing to do from where you currently are.
Alternatively, if it fits what you want to do, Web Workers would be a great solution, since they actually shunt the work into a separate thread.
Having said this, what you're currently doing is extremely inefficient. You push values into an array, and consequently keep checking the ever longer array over and over for the values it contains. You should be using object keys for the purpose of de-duplication instead:
var allPoints = {};
// for (...) ...
if (!allPoints[address]) { // you can even omit this entirely
allPoints[address] = true;
}
// later:
allPoints = allPoints.keys();
First of all, avoid the multiple this._clusterData[i] calls. Extract it to a variable like so:
var allPoints = [];
var current;
for (var i = 0, cLength = this._clusterData.length; i < cLength; i+=1) {
current = this._clusterData[i];
if (allPoints.indexOf(current.attributes.PropertyAddress) == -1) {
allPoints.push(current.attributes.PropertyAddress);
this._DistClusterData.push(current)
}
}
This should boost your performance quite a bit :-)
As others already pointed out, you can do this asynchronously, so the browser remains responsive.
It should be noted however that the indexOf operation you do can become very costly. It would be better if you would create a Map keyed by the PropertyAddress value. That will take care of the duplicates.
(function (clusterData, batchSize, done) {
var myMap = new Map();
var i = 0;
(function nextBatch() {
for (data of clusterData.slice(i, i+batchSize)) {
myMap.set(data.attributes.PropertyAddress, data);
}
i += batchSize;
if (i < clusterData.length) {
setTimeout(nextBatch, 0);
} else {
done(myMap);
}
})();
})(this._clusterData, 1000, function (result) {
// All done
this._DistClusterData = result;
// continue here with other stuff you want to do with it.
}.bind(this));
Try considering adding to the array asynchronously with a list, for a set of 1000 records at a time, or for what provides the best performance. This should free up your application while a set of items is added to a list.
Here is some additional information: async and await while adding elements to List<T>

What is the JavaScript equivalent to Ruby's Enumerable#cycle?

In Ruby, there's a method called, Enumerable#cycle, which allows for repeated loops by x number of times in a collection. I'm looking for something similar in JavaScript but could not find an equivalent.
Does anyone know of a JavaScript equivalent to Ruby's, Enumerable#cycle?
Context: I am trying to loop over the same array in JavaScript -- twice. Once iteration reaches the end, I would like the iteration to start from the beginning of the array and eventually stop. The stopping part is not hard -- it's the cycling of the iteration that I'm trying to achieve in JavaScript. I've been able to do this in Ruby, however, with said method.
I think there is nothing inbuilt which is equivalent to this but if you need to you can follow this post for more detail:
Javascript call a function several times with the arguments.
Apart from the options provided there, you could also extend the Array.prototype to enable this method:
Array.prototype.cycle = function (n, callback) {
for (var i = 0; i < n; i++) {
this.forEach(callback);
}
}
and, could use this like following:
a = [1, 2, 3, 4]
a.cycle(2, function (i) { console.log(i); });
// this will print all the values two times
There is no JavaScript built in (or even proposed to my knowledge) that can do this. However, it's not too hard to accomplish yourself.
Let's assume for a minute that your talking about Arrays, and let's waive away discussion about modifying prototypes or not. The simple version of what you want could be:
Array.prototype.cycle = function(cycleCount, callback) {
for(var i = 0; i < cycleCount; i++) {
this = this.map(callback);
}
}
Let's assume you have an array of numbers. Then you could call this by doing:
myNumberArray.cycle(2, function(num, i) {
return num * 2;
});
We could even get fancy, and let you specify a different action for each cycle:
Array.prototype.cycle = function(cycleCount, callback) {
if(Object.prototype.toString.call( callback ) === '[object Array]') {
if(callback.length === cycleCount) {
for(var i = 0; i < cycleCount; i++) {
this = this.map(callback[i]);
}
} else {
// Uhoh, we don't have the right number of callbacks
throw new Error('If using multiple callbacks, the umber of callback\'s must match the number of cycles');
}
} else {
for(var i = 0; i < cycleCount; i++) {
this = this.map(callback);
}
}
}
There is more error checking you would need to do to make that robust, but you get the idea. :)

JavaScript converting an array to array of functions

Hello I'm working on a problem that requires me to change an set array of numbers into an array that returns the original numbers as a function. So we get a return of a2 instead of a[2].
I dont want the answer I just need a hint. I know i can loop through the array and use .pop() to get the last value of the array, but then I dont know how to convert it to a function from there. any hints?
var numToFun = [1, 2, 3];
var numToFunLength = numToFun.length;
for (var i = 0; i < numToFunLength; i++) {
(function(num){
numToFun.unshift(function() {
return num;
});
}(numToFun.pop()))
}
DEMO
basically it pops out a number from the last, builds a function with that number returned, and put back into the first of the array. after one full cycle, all of them are functions.
here's the catch: how this works, it's up to you to research
why the loop does not look like the straightforward pop-unshift:
for (var i = 0; i < numToFunLength; i++) {
numToFun.unshift(function() { //put into first a function
return numToFun.pop() //that returns a number
});
}
and why i did this: (HINT: performance)
var numToFunLength = numToFun.length;
There's three important steps here:
Extract the number value from the array. Within a loop with an iterator of i, it might look like this:
var num = numArray[i];
This is important, because i will not retain its value that it had when you created the new function - it'll end up with the last value it had, once the for loop is finished. The function itself might look like this:
function() { return num; }
There's no reference to i any more, which is important - to understand better, read about closures. The final step would be to add the new function to the array of functions that you want.
...and you're done!
EDIT: See other's answers for good explanations of how to do this right, I will fix mine also though
As others have pointed out, one of the tricky things in javascript that many struggle with (myself included, obviously) is that scoping variables in javascript is dissimilar to many other languages; scopes are almost purely defined by functions, not the {} blocks of, for example, a for loop, as java/C would be.
So, below you can see (and in other answers here) a scoping function can aid with such a problem.
var numArray = [12, 33, 55];
var funcArray = [];
var numArrLength = numArray.length; // Don't do this in for loop to avoid the check multiple times
for(var j=0; j < numArrLength; j++) {
var scopeMe = function() {
var numToReturn = numArray[j];
console.log('now loading... ' + numToReturn);
var newFunc = function() {
return numToReturn;
};
return newFunc;
}();
funcArray.push(scopeMe);
};
console.log('now me');
console.log(funcArray);
console.log(funcArray[0]());
console.log(funcArray[1]());
console.log(funcArray[2]());
console.log(funcArray[1]()); // To ensure it's repeatable
EDIT my old bad answer below
What you'll want to do is something like
var funcArray = [];
for(...) {
var newFunc = function() {
return numArray.pop();
}
funcArray.push(newFunc);
}
The key here is that functions in javascript can be named variables, and passed around as such :)

Categories

Resources