JavaScript scope, break for cycle from function callback - javascript

I need to break cycle if proccess = true, but it's undefined.
var mapFound;
var locations = {$addressTest};
for (var i = 0; i < locations.length; ++i) {
getCoordinates(
locations[i],
function(proccess) {
}
)
if(proccess) { break; }
}

The problem seems to be basically that getCoordinates() makes an asynchronous call.
By the time the loop is over you haven't received even the first response, based on your question text, so you need to use another solution.
I mean by this that you won't be able to break the cycle, because by the time the cycle is over you still don't know if process is true.
Depending on your implementation you might want to take a look at promises. Although it might be easier to wrap the whole thing in a function that executes a callback:
function getCoords(locations, name, callback){
for (var i = 0; i < locations.length; i++) {
getCoordinates( locations[i], name,
function(proccess) {
if(process){
console.log("Map found in "+locations[i]);
callback.call();
}
}
);
}
}
getCoords({$addressTest}, {$name}, function(){
// Place here whatever you want to do once the map is found.
// you can get the map location by passing it as an argument for the callback.
});

Related

How can I make a for loop in Javascript that will set timeouts from an array?

Background (You might want to skip this)
I'm working on a web app that animates the articulation of English phonemes, while playing the sound. It's based on the Interactive Sagittal Section by Daniel Currie Hall, and a first attempt can be found here.
For the next version, I want each phoneme to have it's own animation timings, which are defined in an array, which in turn, is included in an object variable.
For the sake of simplicity for this post, I have moved the timing array variable from the object into the function.
Problem
I set up a for loop that I thought would reference the index i and array t to set the milliseconds for each setTimeout.
function animateSam() {
var t = [0, 1000, 2000, 3000, 4000];
var key = "key_0";
for (var i = 0; i < t.length; i++) {
setTimeout(function() {
console.log(i);
key = "key_" + i.toString();
console.log(key);
//do stuff here
}, t[i]);
}
}
animateSam()
However, it seems the milliseconds are set by whatever i happens to be when the function gets to the top of the stack.
Question: Is there a reliable way to set the milliseconds from the array?
The for ends before the setTimeout function has finished, so you have to set the timeout inside a closure:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
for (var i = 0; i < t.length; i++) {
(function(index) {
setTimeout(function() {
alert (index);
key = "key_" + index.toString();
alert (key);
//do stuff here
}, t[index]);
})(i);
}
}
Here you have the explanation of why is this happening:
https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b
The for loop will loop all elements before the first setTimeout is triggered because of its asynchronous nature. By the time your loop runs, i will be equal to 5. Therefore, you get the same output five times.
You could use a method from the Array class, for example .forEach:
This ensures that the function is enclosed.
[0, 1000, 2000, 3000, 4000].forEach((t, i) => {
setTimeout(function() {
console.log(i);
console.log(`key_${i}`);
//do stuff here
}, t)
});
Side note: I would advise you not to use alert while working/debugging as it is honestly quite confusing and annoying to work with. Best is to use a simple console.log.
Some more clarifications on the code:
.forEach takes in as primary argument the callback function to run on each of element. This callback can itself take two arguments (in our previous code t was the current element's value and i the current element's index in the array):
Array.forEach(function(value, index) {
});
But you can use the arrow function syntax, instead of defining the callback with function(e,i) { ... } you define it with: (e,i) => { ... }. That's all! Then the code will look like:
Array.forEach((value,index) => {
});
This syntax is a shorter way of defining your callback. There are some differences though.
I would suggest using a function closure as follows:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
var handleAnimation = function (idx) {
return function() {
alert(idx);
key = "key_" + idx.toString();
alert(key);
//do stuff here
};
}
for (var i = 0; i < t.length; i++) {
setTimeout(handleAnimation(i), t[i]);
}
}
I this example you wrap the actual function in a wrapper function which captures the variable and passes on the value.

Weird index issue in Backbone click event callback

I have this code, it's independent and isolated. The problem I am having is that the index i is starting at 1 instead of starting at 0. I have no idea why this could be, and doesn't seem to have anything to do with the closure that I am pushing into the deletes array...but I can't be sure, no idea what the issue is.
onClickResetAll: function (event) {
event.preventDefault();
var deletes = [];
Object.keys(collections).forEach(function (key) {
if (collections.hasOwnProperty(key)) {
var coll = collections[key];
for (var i = 0; i < coll.models.length; i++) {
deletes.push(function (callback) {
var index = i; //i starts at 1, not 0 !!!
coll.models[index].deleteModel(function (err, resp, x) {
console.log(err, resp, x);
if(err){
callback(err);
}
else{
callback(null,null);
}
});
});
}
}
});
async.parallel(deletes,function(err,results){
Backbone.Events.trigger('bootRouter', '+refreshCurrentPage');
});
}, //end of onClickResetAll callback function
//end
The problem isn't really that i starts at one, the problem is that i will be coll.models.length for every function in deletes. Why would that be? Well, each function is sharing the same i and i won't be evaluated until the functions inside deletes are actually called.
The solution is to force i to be evaluated when it has the value you want (i.e. evaluated i when you're building the callback function). There are various solutions and they're all variations on the "wrap it in a function to break the reference" theme:
Use an iterator with a callback function instead of a plain for loop:
coll.each(function(model, i) {
// `model` is the model from the collection, `i` is the loop index.
});
You can use each here because Backbone collections have a bunch of Underscore functions built in.
Wrap the loop body in an SIF:
for(var i = 0; i < coll.models.length; ++i)
(function(i) {
//...
})(i);
Use a separate function to build your functions:
function make_deleter(coll, i) {
return function(callback) {
coll.models[i].deletedModel(function(err, resp, x) {
//...
}
}
}
//...
for(var i = 0; i < coll.models.length; ++i)
deletes.push(make_deleter(coll, i));
They all do pretty much the same thing: add an extra function call into the mix to force i to be evaluated (rather than just referenced) on each iteration of the loop.
In a Backbone situation, 1 would probably be the most natural and you wouldn't even need your troublesome i with that approach.
another solution to this is using async.each or async.eachSeries instead of async.parallel. using the first 2 avoids pushing to an array of functions altogether.

Javascript ajax loop execution

I have 2 questions:
1- How do I set a delay when an ajax post is executing many times ?
2 -How to run some_multi_ajax_function() when the variable exe_counter reaches 0 ?
here is my code:
for (i = 0; i < some_data_list.length; i++) {
exe_counter=1;
data = some_data_list[i];
// Many ajax posts will be executed here. In the end exe_counter will be set to 0;
some_multi_ajax_function(data);
}
function some_multi_ajax_function(data){
$.ajax({
...
}.done(function(d) {
// here it could be used another ajax function
exe_counter = 0;
});
}
UPDATE
I am sorry, I have bad explained.
I want execute
data = some_data_list[1]; // second iteration. after that and others
some_big_function(data); // this can't start if exe_counter != 0
when exe_counter==0 its mean that some_multi_ajax_function() is completely done.
Here you have the answer to the first question:
Execute script after specific delay using JavaScript
Now for the 2nd question, there are several problems with your code:
To call the same function when it reaches 0 would create an infinite loop.
exe_counter isn't changed at the end. It will be changed when the first ajax return in the example you gave.
You should set exe_counter outside the loop, and assing to it the value of some_data_list.length
then in some_multi_ajax_function you do exe_counter--;
then you test for 0 and call the function which you want to call on finalization but this function can't be some_multi_ajax_function()
so it goes like this:
exe_counter = some_data_list.length;
for (i = 0; i < some_data_list.length; i++) {
data = some_data_list[i];
// Many ajax posts will be executed here. In the end exe_counter will be set to 0;
some_multi_ajax_function(data);
}
function end_function() {
// whatever
}
function some_multi_ajax_function(data){
$.ajax({
...
}.done(function(d) {
// here it could be used another ajax function
exe_counter--;
if (exe_counter == 0) end_function();
});
}
This is untested code just to explain the concepts. Also try not to use global variables (the ones without var clause). They make your code messy and hard to maintain.
You question isn't very clear but it sounds like you want to use when to manage the ajax requests rather than have them all nested. You can pass an array of ajax promises into when by using the apply method.
// this function returns a promise
function some_multi_ajax_function(data){
return $.ajax({
// ...
});
}
function getPromises() {
for (var i = 0; i < some_data_list.length; i++) {
var promises = [];
exe_counter = 1;
var data = some_data_list[i];
// push all promises into an array
promises.push(some_multi_ajax_function(data);
}
return promises;
}
var exe_counter = 0;
// use $.when to call all the promises and once they're
// completed set exe_counter to 0, or whatever you need to do
$.when.apply(null, getPromises()).then(function () {
exe_counter = 0;
});

Check several values retrieved asynchronously from indexed DB

I need to retrieve several values from an IndexedDB, check if all of them fulfill some constraint (different for all of them) and if so call a function. To illustrate this better imagine that calls to IndexedDB were sychronous, we would have something like.
function myFunc(varNames, conditions) {
for(var i = 0; i < varNames.length; i++) {
if(!isGood(db.get(varNames[i]), conditions[i])) {
return;
}
}
doStuff();
}
Since IndexedDB is are asynchronous I do not konw how to do it. Using a callback in every get is not really feasible since the call to doStuff depends on all the gets. Accumulating the results in a global variable would not work either because myFunc is called more than one. I tried something like:
function myFunc(varNames, conditions) {
var valid = 0;
checker() {
valid++;
if(valid == varNames.length) {
doStuff();
}
}
for(var i = 0; i < varNames.length; i++) {
db.get(varNames[i], function(val) {
if(isGood(val, conditions[i]) {
checker();
}
});
}
}
But that does not seems to work either. Any ideas?
You can make the DB calls one at a time, and use the success callback to make the next DB call. Something like this:
function myFunc(varNames, conditions){
if(varNames.length){
var name = varNames.shift(),
condition = conditions.shift();
db.get(name, function(val){
if(isGood(val, condition)){
myFunc(varNames, conditions);
}
});
} else {
doStuff();
}
}

Javascript wait for a function to return

I have a function "add_floor(xxx)" in a loop (see bellow) that take some time to complete, so i would like to know how to wait for the return of this function before moving to the next step which is in my case "add dawfeature points". I understand that I could use a callback method but then I would need to restart the complete loop that goes over the json elements in the calledback function. Not really sexy, so is there a way to avoid this ?
function add_maps_from_json(json) {
for (var i = 0; i < json.maps.length; i++) {
// add the maps to the combox box and display last one
add_floor(json.maps[i].mapName);
// add the dawfeature points to the map
for (var j = 0; i < json.maps[i].APs.length; j++) {
var idx = find_map_index();
console.log(idx);
var map = mapObjectArray[idx];
etc...
etc...
}
}
}
You can make the function return a value to a variable. That way the program will pause until the function as run. Like this:
var maps = add_maps_from_json(json);
Or you can use plugin.js and do module calls.
http://requirejs.org/docs/plugins.html
as you have said you have to add a callback parameter to add_floor and for managing your loop you can do async loop, like this:
//change the add_floor function
function add_floor(mapName, callback){
//your code
//you probably have some kind of ajax call or setTimeout
//then call your callback after your ajax response or timer callback
//....
callback();
//....
}
function add_maps_from_json(json) {
doLoop(json, 0)
}
function doLoop(json, i) {
if(i < json.maps.length) return;
add_floor(json.maps[i].mapName, function () {
// add the dawfeature points to the map
for (var j = 0; i < json.maps[i].APs.length; j++) {
var idx = find_map_index();
console.log(idx);
var map = mapObjectArray[idx];
//etc...
//etc...
}
doLoop(json, i + 1);
});
}
If add_floor doesn't do an ajax call, then your code will work fine as it is now.
If add_floor DOES do an ajax call, then you can put the inner for loop inside a separate function that you then use as the callback for the ajax call in add_floor.

Categories

Resources