I am writing a tool that will loop through a list of id's (represented by id in id_list). We check a cache object to see if we already have a value for the id. If we don't already have a value for the given id, we'll need to make a get request to get the associated value and then add it to the cache.
In the time it takes to do one async get request, the entire loop runs. This means the cache is never actually used. Is there anyway I can require the get request to finish before continuing the loop? Normally I would chain the request through the onSuccess function of the previous, but since there's a change, no request will be made.
cache = {};
var rating;
for (id in id_list){
if (id in cache){
rating = cache[id];
}else{
rating = $.get(~~~async get request happens here~~~);
cache[id] = rating;
}
$(".result").append(rating);//display result in ui
}
You can't use a for loop if you want it to wait between each iteration. A common design pattern is to create a local function for a given iteration and then call it each time the async operation finishes.
Assuming id_list is an object with properties, you could do it like this:
var cache = {};
var ids = Object.keys(id_list);
var cntr = 0;
function next() {
var id;
if (cntr < ids.length) {
id = ids[cntr++];
// see if we can just get the value from the cache
if (id in cache) {
$(".result").append(cache[id]);
// schedule next iteration of the loop
setTimeout(next, 1);
} else {
// otherwise get rating via Ajax call
$.get(...).then(function(rating) {
$(".result").append(rating);
// put rating in the cache
cache[id] = rating;
next();
});
}
}
}
next();
Or, if id_list is an array of ids, you can change it to this:
var cache = {};
var cntr = 0;
var id_list = [...];
function next() {
var id;
if (cntr < id_list.length) {
id = id_list[cntr++];
// see if we can just get the value from the cache
if (id in cache) {
$(".result").append(cache[id]);
// schedule next iteration of the loop
setTimeout(next, 1);
} else {
// otherwise get rating via Ajax call
$.get(...).then(function(rating) {
$(".result").append(rating);
// put rating in the cache
cache[id] = rating;
next();
});
}
}
}
next();
Related
I have two functions that I am trying to run when I load the page. dataRetrieve() gets the data from a firebase collection. populate() is supposed to populate a boxlist with the entries retrieved from dataRetrieve(). The main problem is that it lists the array as empty when I run populate() after dataRetrieve() no matter what I try. The last thing I tried was this:
async function dataRetrieve(){
const getAdmins = firebase.functions().httpsCallable('getAdmins');
// Passing params to data object in Cloud functinon
getAdmins({}).then((results) => {
admins = results;
console.log("admins retrieved");
console.log(admins);
}).then(() => {
populate();
});
}
async function populate(){
let list = document.getElementById("user-list");
//loop through users in out Users object and add them to the list
for (var i = 0; i < admins.length; i++) {
let newItem = document.createElement('option');
newItem.innerHTML = admins[i].first + " " +admins[i].last;
newItem.id = admins[i].uid;
if (i == 0) {
newItem.className = "active";
}
console.log(newItem.innerHTML + " " + newItem.id)
list.appendChild(newItem);
}
updateResponse(list.firstChild);
list.size = admins.length;
console.log(document.getElementById("user-list").size)
//collect all the list items
let listItems = list.querySelectorAll('option');
//loop through the list itmes and add a click listener to each that toggles the 'active' state
for (var i = 0; i < listItems.length; i ++) {
listItems[i].addEventListener('click', function(e) {
if (!e.target.classList.contains('active')) {
for (var i = 0; i < listItems.length; i ++) {
listItems[i].classList.remove('active');
}
e.target.classList.add('active');
updateResponse(e.target);
}
})
}
}
also, admins is a global variable listed at the start of the script:
var admins = [];
I am trying to run all this onload so I can instantly generate the list
I thought that .next would cause it to wait to get the values before running, but even making results a parameter and transferring it directly into the function that way gives an undefined array. I don't understand why the function insists on calling on old data. Pls help.
I'm not sure what updateResponse function does. If it's not returning a promise then I'd make the populate function synchronous first. Also do you really need to use admins array somewhere else apart from populate function that it is a global variable? If not then I'd just pass it as a parameter.
async function dataRetrieve() {
const getAdmins = firebase.functions().httpsCallable('getAdmins');
// Passing params to data object in Cloud function
const results = await getAdmins({})
console.log("admins retrieved");
console.log(results);
// Passing results in populate function
populate(results.data)
// If your function returns an array, pass the array itself
}
function populate(admins) {
let list = document.getElementById("user-list");
//loop through users in out Users object and add them to the list
// Using a for-of loop instead so no need to worry about checking the length here
for (const admin of admins) {
let newItem = document.createElement('option');
newItem.innerHTML = admin.first + " " + admin.last;
newItem.id = admin.uid;
//if (i == 0) {
// newItem.className = "active";
//}
console.log(newItem.innerHTML + " " + newItem.id)
list.appendChild(newItem);
}
updateResponse(list.firstChild);
// rest of the logic
}
I guess you know how to check when the page loads. call the retrieve function when the page is loaded. Then you should call the populate function at the end of the retrieve function. this makes sure that the populate function is called after you get all the data
Im trying to make a webscraper(educational puposes), and I got really far, but this little issue is bugging me.
I made a request callback function, and im trying to get lines 75-78 to work. However to get this to work, I need PDF_LISTS and PDF_LINKS to initilaze to the right values.
I've already tried to make them global variables, and what not, for some reason that doesnt work. So my question is: How do I make a callback function that will call that for loop (75-78) and succesfully initilaze PDF_LISTS and PDF_LINKS to the correct values ?
(Dont worry I use this on educational content, with the prof's permission). First time posting here!
// URL_LINKS has the pdf links of the pages
PDF_LINKS = [];
// URL_LIST has the names of the pdf links
PDF_LIST = [];
function fillPDF(callback) {
request(url, function(err, res, body) {
$ = cheerio.load(body);
links = $('a'); //jquery get all hyperlinks
$(links).each(function(i, link) {
var value = $(link).attr('href');
// creates objects to hold the file
if (value.substring(value.length - 3, value.length) == "pdf") {
PDF_LINKS[i] = $(link).attr('href');
PDF_LIST[i] = $(link).text();
}
})
});
}
// must decleare fillPDF variable or else you wont initilze teh variables
fillPDF() {
//HERE I WANT PDF_LINKS and PDF_LIST to be intialized to 33.....
}
for (j = 0; j < PDF_LIST.length; j++) {
request(PDF_LINKS[j]).pipe(fs.createWriteStream(PDF_LIST[j]));
}
You may push your values into arrays using array's push method, avoiding array's element to be undefined.
You can put your final for loop into a function, and then use fillPDF();
You also need to call fillPDF's callback once the request is over.
PDF_LINKS = [];
PDF_LIST = [];
function fillPDF(callback) {
request(url, function(err, res, body) {
$ = cheerio.load(body);
links = $('a');
$(links).each(function(i, link) {
var value = $(link).attr('href');
if (value.slice(-3) == "pdf") {
PDF_LINKS.push(value);
PDF_LIST.push($(link).text());
}
})
callback();
});
}
function writePDF() {
for (j = 0; j < PDF_LIST.length; j++) {
request(PDF_LINKS[j]).pipe(fs.createWriteStream(PDF_LIST[j]));
}
}
fillPDF(writePDF);
I have an CSV parsing function in JavaScript which gets data (movie names) from CSV and gets data using Ajax call in loop.
movies = new Array();
for (var i = 1; i < allData.length; i++) {
var mName = allData[i][0];
var mPath = allData[i][1];
// console.log(decodeURIComponent(mName));
$.get(apiCall, function showData(data) {
if (data) {
mData = data.results;
if (mData.length > 1) {
var urlData = new URLSearchParams(this.url);
var movie_name = urlData.get('query');
movies.push(movie_name);
}
}
})
}
If data got more then one record for any movie it will save it as a conflict in array.
Problem is, I can access movies array inside inner if (but it is in iteration so I can't use that) and at loop end it is not accessible. How can I access that?
You should not make api calls inside a for loop. Instead do this,
movies = new Array();
function makeApiCallForEntireArray(index, arr, cb){
if(arr.length == index){
cb(true);
return;
}
$.get(apiCall, function showData(data) {
if (data) {
mData = data.results;
if (mData.length > 1) {
var urlData = new URLSearchParams(this.url);
var movie_name = urlData.get('query');
movies.push(movie_name);
}
}
makeApiCallForEntireArray(index+1, arr, cb);
})
}
makeApiCallForEntireArray(0, allData, function(){
//api calls finished
//movie accesssible here with all the data
});
You will not be able to access the content added in movies array at the end of the loop because ajax requests are still in progress. You need to do this some other way so that you can be sure that its end of asynch ajax calls.
Im going to use the answer of #Jaromanda X in my question here Can't get the summation in for loop
Promise.all(allData.map(function(d) {
return $.get(apiCall, function showData(data){
return data.results;
});
})).then(function(res) {
//push your movies here...the result of your apiCall is inside the res variable
});
Hi we have an angular js controller that call a service function:
var decodedValue = StaticTableService.DecodificaAllergeni(listObj);
this service function get an array of integer. We want to decode this integer in string from StaticTableService. There decodedValue will be a string array, where each string id the decoded string from int. If int number is 0 not be decoded.
This functiong
DecodificaAllergeni: function(allergeni)
{
var elenco = Array();
var loopPromises = []; //Promise for loop
for(i=0;i<allergeni.length;i++)
{
var idAllergene = allergeni[i];
if(idAllergene!=0)
{
console.log("StaticTable IdAllergene: "+idAllergene);
loopPromises.push(AllergeniService.get(idAllergene));
console.log(idAllergene);
}
}
//Wait all
$q.all(loopPromises).then(function () {
console.log('forEach loop completed.');
});
},
I declare promise array and wait $q.all(...) for waiting all call are ending.
Every call is made to AllergeniService.get(idAllergene) where AllergeniService is another service with same element and i match single id with id element. If match return a single element.
get: function(idAllergene)
{
var deferred = $q.defer();
for (i = 0; i < allergeni.length; i++) {
//if(idAllergene == allergeni[i].id)
// ...
}
console.log("Id allergene che ha chiamato: "+idAllergene);
deferred.resolve(1);
return deferred.promise;
}
In this case a return always deferred resolve with 1 as parameter.
The problem is that this call is made only one time with first element.
How can call a function n times with different parameter and wait until all call are terminated?
Thanks
I have 2 loops, the first loop is a very short loop and run very fast and the second loop is a long loop and takes much more time.
I am passing info from the first loop into the second loop, and when the second loop finishes it will call "finish", and it needs to then trigger the first loop to run its second iteration,it will pass the second iteration info into the second loop again, and when the second loop finishes again, it will call
finish" then trigger the first loop to run the third iteration.
And the process continues until the first loop finishes all its iteration.
How would i approach it? I have tried the below but it stops after the first iteration for the first loop. I just need the loop to stop after each iteration and when ondemand(a trigger) it will go to the next iteration.
for (var i=0; i<from.length; i++) {
if (loopfinished=true){
}}
Or maybe run it in a different way but i am not sure if it is possible or not.
basically I will have different users which i have to run in a loop, also loop through messages for each person. But i have to wait til the message loop is completed before iterate to the next person, because i have to set sessionstorage for the person's message, if it doesn't wait for the message loop to complete, then it won't save to the correct person.
var people=["user1#server","user2#server","user3#server"]
// function to loop through messages for each person
for (var i=0; i<from.length; i++) {
//load all the info here, when complete it will call done
if(done){
// when completed first person set people[2], when people[2] is done run people[3]
}
}
Edit
var messageno=0;
var loopfinished=true;
var from=["user1#server","user2#server","user3#server"]
for (var i=0; i<from.length; i++) {
if (loopfinished){
console.log(from[i]);
var person=from[i]
connection.mam.query(jid, {
"with": person,"before": '',"max":"10",
},onMessage: function(message) {
var message ="<div id=log2><br>'"+msg_data.message+"'</div>"
messageno= messageno+1;
console.log( messageno);
if(messageno==count){
loopfinished=true;
console.log("Inner loop completed");
console.log(loopfinished);
}
return true;
}}
Edit Strophe RSM plugin
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define("strophe.rsm", [
"strophe"
], function (Strophe) {
factory(
Strophe.Strophe,
Strophe.$build,
Strophe.$iq ,
Strophe.$msg,
Strophe.$pres
);
return Strophe;
});
} else {
// Browser globals
factory(
root.Strophe,
root.$build,
root.$iq ,
root.$msg,
root.$pres
);
}
}(this, function (Strophe, $build, $iq, $msg, $pres) {
Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
Strophe.RSM = function(options) {
this.attribs = ['max', 'first', 'last', 'after', 'before', 'index', 'count'];
if (typeof options.xml != 'undefined') {
this.fromXMLElement(options.xml);
} else {
for (var ii = 0; ii < this.attribs.length; ii++) {
var attrib = this.attribs[ii];
this[attrib] = options[attrib];
console.log("done5");
}
}
};
Strophe.RSM.prototype = {
toXML: function() {
var xml = $build('set', {xmlns: Strophe.NS.RSM});
for (var ii = 0; ii < this.attribs.length; ii++) {
var attrib = this.attribs[ii];
if (typeof this[attrib] != 'undefined') {
xml = xml.c(attrib).t(this[attrib].toString()).up();
console.log("done6");
}
}
return xml.tree();
},
next: function(max) {
var newSet = new Strophe.RSM({max: max, after: this.last});
return newSet;
},
previous: function(max) {
var newSet = new Strophe.RSM({max: max, before: this.first});
return newSet;
},
fromXMLElement: function(xmlElement) {
for (var ii = 0; ii < this.attribs.length; ii++) {
var attrib = this.attribs[ii];
var elem = xmlElement.getElementsByTagName(attrib)[0];
if (typeof elem != 'undefined' && elem !== null) {
this[attrib] = Strophe.getText(elem);
if (attrib == 'first') {
console.log("done6");
this.index = elem.getAttribute('index');
}
}
}
}
};
}));
As i understand, your query call issues an async operation, and you can't get the result in the same call context which issues the request.
The onMessage event will fire only when your loop is ended, so you need to provide a means to handle the context in the callback itself.
Also if you issue all the asynchronous requests inside a loop you can't infer the order in which the operations finish.
var count = users.length;
for (var u in users)
{
var user = users[u];
query({onMessage:(function(user){return function(message){
count --;
processUserMessage(user, message); // some operation on a user and a message
if (count === 0)
allFinished();
};})(user)});
}
// At this point, none of the requests have returned. They will start arriving later.
If you need a strict order, then you have to issue the requests one by one, but not in a loop, but the next request should be issued in the previous request's callback.
var processChunk = function(user)
{
query({onMessage:function(message){
processUserMessage(user, message);
var nextUser = findNextUser(user);
if (nextUser)
processChunk(nextUser);
else
allFinished();
}});
};
processChunk(firstUser);
// At this point, none of the requests have returned. They will start arriving later.