getJSON function is skipped in foreach loop - javascript

Hello I have the following code:
listItems.forEach( item => {
$.getJSON('https://www.googleapis.com/youtube/v3/videos?part=statistics&id=KS_Vw5DMlEI&key=AIzaSyAlUj6YMmrt-0s34dQ-LdywneUzZhmsVYA', function(data) {
const viewsin = data.items[0].statistics.viewCount;
localStorage.setItem('ViewsCounter3', viewsin );
alert('inside: ' + viewsin);
});
alert('outside: ' + localStorage.getItem('ViewsCounter3'));
});
My problem is this: Neither every pass in my foreach loop, the code outside the getJSON function is executed first and then the code in the function. The order of the alerts looked like this:
"outside: View Count", "inside: View Count", "outside: View Count", etc...
I would like to have the inside alert executed first and then the outside alert when it is finished. I already tried await but this did not work, because I think it is an asynchronous function.
Is there a way to keep the code running after the code has been executed in the getJSOn function?
Could someone please help me with my problem. I would be happy about answers :)

forEach loop doesn't work for defining an async step. Try for...of loop with await
for(let item of listItems){
await $.getJSON('https://www.googleapis.com/youtube/v3/videos?part=statistics&id=KS_Vw5DMlEI&key=AIzaSyAlUj6YMmrt-0s34dQ-LdywneUzZhmsVYA')
.done(data=>{
const viewsin = data.items[0].statistics.viewCount;
localStorage.setItem('ViewsCounter3', viewsin );
alert('inside: ' + viewsin);
});
alert('outside: ' + localStorage.getItem('ViewsCounter3'));
};

Related

how do I assign a returned value from an async function to a variable

I am new to JavaScript and have been trying to read up a lot on why this is not working. Here is my code. I have also read a number of articles here on stack overflow but still feeling dense
Also if my title does not make sense, please suggest an edit
listRef.listAll()
.then(response => {
let files = []
response.items.forEach(item => {
var text
getText(item.name).then(res=>{text = res});
const id = {uid: guid()}
const url = item.getDownloadURL().then(url => {return url} )
const gsurl = `gs://archivewebsite.appspot.com/${folder}/${item.name}`
files.push({...item, name:item.name, url, gsurl, id:id.uid, text})
});
this.files = files;
})
.catch(error => console.log(error));
async function getText(docID) {
var docRef = firestore.collection("recipes").doc(docID);
let doc = await docRef.get()
if (doc.exists){
return doc.data().text
}
}
that code "works" in that it logs the response to the console but the text variable is a pending promise object.
I understand that async functions return a promise so when I call getText I need to use .then - what I am struggling with and have refactored this code a few times is this:
how can I assign the value of doc.data().text to a variable to be used later in other words, how can var text be an actual string and not a promise object pending
Also for my own learning on javascript inside the async function if I replace
if (doc.exists){
return doc.data().text
}
with
if (doc.exists){
return Promise.resolve(doc.data().text)
}
I get the same result in console.log - is this expected? is return simply short hand for the handler to resolve the promise?
I have also refactored this code to be non async and I get the same result where my var text is basically a pending promise and never the resolved data
Thanks for your help - also any articles to help explain this to me would be great! I have been going through courses on udemy but little confused by this right now
Actually you are assigning the complete promise to the variable text
Replace
var text = getText(item.name).then(res=>console.log(res))
by
var text = await getText(item.name);
OR
var text
getText(item.name).then(res=>{text = res});
Of course text is going to be a Promise. Promise.then() always returns a Promise.
Consider this code:
function doA(n) {
// do something here...
console.log("A" + n);
}
asnyc function doB(n) {
// do something here...
console.log("B" + n);
}
doA(1);
doA(2);
doB(3); // async
doA(4);
doB(5); // async
doB(6); // async
doA(7);
What do you expect the output to be?
1, 2, 4, and 7 will always be in order, because they are executed synchronously.
3 will not ever print before 1 and 2. Likewise, 5 and 6 will not ever print before 1, 2, and 4.
However, 3, 5, and 6 can be printed in any order, because async functions do not guarantee execution order once created.
Also, 4 can print before 3. Likewise, 7 can print before 5 and 6.
Basically, think of async functions as a parallel task that runs independently (although not really; single thread JS only simulates this behavior). It can return (fulfill/reject) at any moment. For this reason, you cannot just simply assign a return value of an async function to a variable using synchronous code - the value is not guaranteed to be (and probably is not) available at the moment of synchronous execution.
Therefore you need to put all the code that requires the value of text to be set into the callback block of the Promise, something like this:
getText(item.name).then((text) => {
// put everything that uses text here
});
This can of course lead to the infamous "callback hell", where you have layers inside layers of async callback. See http://callbackhell.com for details and mitigation techniques.
async/await is just one of the newer ways to do the same thing: MDN has an excellent article here: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
OK I worked with someone at work and found a solution - it was related to this post
https://stackoverflow.com/a/37576787/5991792
I was using async function inside a for each loop
the refactored code is here
async function buildFiles(){
let items = await listRef.listAll()
let files = []
for (const item of item.items){
const text = await getText(item.name)
const url = await item.getDownloadURL()
const gsurl = `gs://archivewebsite.appspot.com/${folder}/${sermon.name}`
files.push({...item, name:item.name, url, gsurl, text})
}
return files
}
async function getText(docID) {
var docRef = firestore.collection("recipies").doc(docID);
let doc = await docRef.get()
if (doc.exists){return await doc.data().text}}
buildFiles().then(res=>this.files = res)
Thanks also to #cyqsimon and #Nav Kumar V

How can I ensure an if loop waits for and receives a result before running the next if loop in sequence

I have a javascript function where if loops are not following each other sequentially. I need them to run one after the other. The second loop should not run until the first loop has finished, because it deals with the output of the first.
I use an if loop (loop 1) to call a function in a child iframe (this frame contains mapping elements, and I can't reasonably combine it with the parent frame). This part is working as intended.
The function in the iframe is triggered, and it makes a call to an external service, and awaits a response. When it receives a response it passes either "error" or "ok" back to the parent function by using the 'return' function. This part is working as intended.
The parent receives the response, sets a variable and then should continue on with the next if statement (loop 2) that does something else.
What actually happens is that loop 1 runs, then loop 2 also runs, and loop 2 returns results before loop 1 - which screws things up as loop 2 is meant to be dealing with thee results from loop 1.
jQuery(document).on('click', '.gv-button-delete', function (e) {
e.preventDefault(); //prevent submission
console.log('Intercepted delete button request');
var delLoc = "";
(function ($) {
var delLoc2 = $('.gv-button-delete').attr("href"); //capture default action, because we need the time-valid nonce
delLoc = delLoc2;
}(jQuery));
var objID = document.getElementById("input_4_40").value;
objID = parseInt(objID);
var iframe = document.getElementById("MapFrame1");
var result = "";
if (iframe) { //1st if loop that collects the answer
var iframeContent = (iframe.contentWindow || iframe.contentDocument);
var result = iframeContent.WriteLog(objID); //call the function from the iframe, and hopefully wait for a result.
console.log(result);
}
if (result == "error") { //the second if loop
console.log("Step 5")
console.log("There was an error with removing the feature");
} else if (result == "ok") {
console.log("Step 5")
console.log("The spatial delete completed correctly");
} else {
console.log("Step 5")
console.log("unexpected result of spatial delete")
}
});
the iframe code, becuase it's useful for context.
function WriteLog(objID){
var retireFeatureID = hotspots.getFeature(objID);
var successStatus = "";
retireFeatureID.feature.properties["SystemStatus"] = "Deleted";
hotspots.updateFeature(retireFeatureID.feature, function (err, response) { //This is a syncronous call to another service
if (err) {
console.log("Step 3")
console.log(err);
successStatus = "error";
console.log("successStatus is: " + successStatus);
} else {
console.log("Step 3")
console.log(response);
successStatus = "ok";
console.log("successStatus is: " + successStatus);
}
});
console.log("Step 4")
console.log("Updated the status of feature: " + objID);
console.log("child iframe has variable successStatus as: " + successStatus);
return successStatus;
}
What actually happens is that the console results look like:
Step 4
Step 5
Step 3
The second loop is returning before the first loop has finished and returned a result.
async-await might the answer to your problem.
Here is how it works.
You define a function that sends a response with some delay (maybe because of a network call or something).
async function f() {
// Make network call and return value
return value;
}
And you call this function with an await.
var valueRequired = await f();
if(valueRequired == something) {
doSomeWork();
}
I hope this was clear.
Reference: MDN
Do note that this is not compatible in older browsers, as this is a rather modern JS construct.
This might be due to how callbacks and the javascript event loop work in general, and steps 4 and 5 will be executed first before step 3.
The function response callback will be placed at the end of call stack, which causes the remaining code (step 4 onwards and second if loop) to be executed without waiting for the callback to complete despite the other service code being synchronous.
I would suggest you to either convert the service function to one with a direct return and if possible not using callbacks, or changing the WriteLog function to a callback function by adding a callback argument and invoking it once you get the response from the other service.
JavaScript Event Loop Explained
Why don't you add a flag. This flag can go right after console.log(result). The 2nd if block can be inside a while that does not allow for the code to proceed before this flag is true. This ensures that your 2nd if won't happen before the 1st.

JavaScript - slow down a loop

I have these codes in controller that call webservice using $http get to retrieve some data. Here is the following code:
UPDATE CODES:
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
console.log('data', data.arrBoothDesignatedCoords)
boothDesignatedCoords = data.arrBoothDesignatedCoords;
boolGetBoothCoords = true;
})
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// And a lot of other codes
However, since $http get is asynchronous method, the program will invoke the console log and the codes after immediately and boothDesignatedCoords will be undefined. I do not want that. I want the program to invoke the console log and the codes after ONLY when the webservice consumption is completed. So I did the following using this answer: how to slow down a javascript loop:
go();
function go() {
console.log("hello");
if (boolGetBoothCoords == false) {
setTimeout(go, 1000);
}
else
{
}
}
go()
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// OTHER CODES that uses variable boothDesignatedCoords will be undefined as well
However, I do not know why it will still invoke the console log but the web service consumption is not completed yet, despite using this method. Can someone please help me? Thanks.
setTimeout is asynchronous, so actually you don't really make a difference calling go function.
What will happen is:
call go() function
call setTimeout inside the function - that will schedule go to be called in (roughly) 1s
call console.log immediately after that
What you probably want is to put your console.log in then callback like that:
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
return response.data.arrBoothDesignatedCoords;
})
.then(function(boothDesignatedCoords) {
console.log("boothDesignatedCoords ", boothDesignatedCoords );
});
The other option (not recommended) would be to put console.log in the else part of the if statement in your go function.
In this case, you should also define boothDesignatedCoords before the whole code snippet.
The code that suppose to be run after the response, should be invoked after the response.
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(
strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
console.log('data', data.arrBoothDesignatedCoords)
boothDesignatedCoords = data.arrBoothDesignatedCoords;
boolGetBoothCoords = true;
codeThatUsesTheResponseData();
});
function codeThatUsesTheResponseData() {
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// And a lot of other codes
}

How to run Asynchronous Javascript Functions in order

Hi I am a beginner programmer and I need to run several javascript functions in an order on a page; getcampaignID(), search1(), searchresult(), search2(), searchresult(). I need to retrieve the campaign ID first to send it over to search1(), get the result, then running search2() to get its result next.
I have successfully ran [search1() + searchresult()] before [search2() + searchresult()] by executing the search1() after the </body> tag and adding a setTimeout in searchresult(). However, I am unable to run getcampaignID first without breaking search1() and search2()
My code looks like this: home.html
<script>
getcampaignID() {
//...make AJAX call to get campaignID
campaignID = xmlhttp.responseText.trim();
}
getcampaignID(); //run function
searchresult() {
//...retrieves search results
//...appends to html div
setTimeout(function () {
if (counter == 0) {
counter = 1;
search2();
} else {
clearTimeout(timer);
}
}, 500);
} //end of searchresults
search1() {
//...search1 parameters
//url=?camid = campaignID
//campaignID unidentified
}
search2() {
//...search2 parameters
//url=?camid = campaignID
//campaignID unidentified
}
</script>
<body>
<div id= results1>...</div>
<div id= results2>...</div>
</body>
<script>
search1();
</script>
Things I have tried:
getcampaignID() {
//... all the codes mentioned
search1() {
alert("search1 working");
}
search2() {
alert("search2 working");
}
}
search1();
Problem: search1() won't run, no alerts fired.
getcampaignID() {
var campaignid = "A";
big(campaignid);
}
big
function (campaignid) {
//..all codes
search1() {
alert("search1 working");
}
search2() {
alert("search2 working");
}
search1();
}
search1();
Problem: search1() won't run, no alerts fired.
Summary:
I am looking for a way to add campaignID value in search1(); before search1 runs
What you need is ES6 Promises. Using Promises you could chain them using .then() method and run them one after another and handle results in chain.
Using promises you could write something like
Promise.resolve().then(
search1();
).then(
doSomethingWithResults(results);
).then(
// ....
);
You could find a good introduction to Promises here: https://davidwalsh.name/promises
You can achieve what you are asking with Promises. They can take a little bit of getting used to, but once you get the hand of it, it makes the asynchronous control flow you are having issues with really simple.
If you used a library to perform your AJAX requests, that returned a Promise itself like Fetch, you could write your code like this:
//Fetch will return a promise the resolves with a value of your campaign ID
fetch('http://endpoint.com/campaign')
.then(campaignId => {
//You can pass that ID to each of your searches.
//Promise.all will resolve with an array containing results from each function you passed in, in that order.
return Promise.all([search1(campaignId), search2(campaignId)]);
})
.then(results => {
//Search 1 was the first promise we passed to Promise.all
let search1Results = results[0];
//Search 2 was the second promise we passed to Promise.all
let search2Results = results[1];
//process the results
});
So there are some possibilities:
Nest the functions. Run search2() inside the callback of search1().
Use jQuery and .defer();
Learn about promises and do same as 2. but without jQuery :)

Facebook Api Call Ready

I am having a synchronisation issue in Javascript. Code below. When I make the call to get mutual friends, although my function is still filling the array through the API callback, the printing of "Quite a bunch: 0" happens before the printing of the - console.log(friendID +" -> " + mutualfriends.data.length);
I know this must be a callback / asynch issue but I have no idea how to deal with it. I'm filling the array for a reason - need it to be filled for the next part.
code:
function getMutualFriends(friendID)
{
//console.log(response.data[i].id);
try{
FB.api('/me/mutualfriends/'+friendID, function(mutualfriends) {
//console.log(mutualfriends);
//console.log(mutualfriends.data.length);
console.log(friendID +" -> " + mutualfriends.data.length);
mutualFriendsList.push([friendID,mutualfriends.data.length]);
});
}
catch(err){
console.log('error caught: ' +err);
}
}
function getFriendsList()
{
FB.getLoginStatus(function(response){
FB.api('/me/friends', function(response) {
for(i=0; i<response.data.length;i++)
{
var friendID = response.data[i].id;
console.log(friendID);
friendsList.push(friendID);
}
console.log('Interesting, we gathered: '+friendsList.length+' friends,');
console.log('lets check mutual friends now');
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j]);
}
console.log('Quite a bunch: ' + mutualFriendsList.length);
});//friends
});
}
You'll probably want to turn your "post-condition" code into a callback.
So where you now have:
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j]);
}
console.log('Quite a bunch: ' + mutualFriendsList.length);
You'll want something like:
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j], function(mutualFriendsList) {
console.log('Quite a bunch: ' + mutualFriendsList.length);
});
}
When you set it up like this, your getMutualFriends function can call the callback once it got the result:
function getMutualFriends(friendID, callback)
{
FB.api('/me/mutualfriends/'+friendID, function(mutualfriends) {
mutualFriendsList.push([friendID,mutualfriends.data.length]);
callback(mutualFriendsList);
});
}
This will do the callback once for every call to getMutualFriends. If you want the callback to only trigger once for all friends, you'll need to expand the concept a bit further.
Update:
You could combine the above "per friend" callback with #RGDev's condition to detect the last friend:
for(j=0; j<friendsList.length;j++)
{
getMutualFriends(friendsList[j], function(mutualFriendsList) {
if (mutualFriendsList.length == friendsList.length) {
console.log('Quite a bunch: ' + mutualFriendsList.length);
}
});
}
getMutualFriends is doing an async call on his own (by calling FB.api) so you cannot print the mutualFriendsList until it's done (actually, you are doing may calls to getMutualFiends so your mutualFriendsList won't be accurate until all of them finished their async processing).
Can't you rely on that collection to be async? I don't know what you are doing with that, but maybe (if you are drawing it in the screen by example) you can issue a redraw of that component every time the callback of FB.api('/me/mutualfriends/'+friendID, ...) is done.
I had a similar issue to you in the past, I solved it by using a backbone.js' collection instead of a simple javascript array. Then I bound a listener to the add event of the collection and print the qty of friends every time an object was added to it.
Good luck,
You could use a while loop to wait until the mutualFriendsList.length is equal to friendsList.length. which would mean that all of the ajax functions return functions had been fulfilled.
It would look something like:
while(mutualFriendsList.length != friendsList.length)
{
console.log("Still Waiting");
}
console.log('done')
// do your data processing
A little inelegant, but it should work

Categories

Resources