Global array not updating after .push inside function [duplicate] - javascript

This question already has answers here:
Returning value from asynchronous JavaScript method?
(2 answers)
Closed 7 years ago.
Hey guys anyone who can help my out?
Basically I want to make a breaking news footer that loops through the newsWire array and updates the text automatically. Problem is when I run my console.log(newsWire.length) outside the loadNewswire function it returns a 0, while the console.log inside returns 40 as it should?
Link: http://jsfiddle.net/u8y8zh72/3/
<html>
<head>
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<style>
footer {
height: 75px;
position: fixed;
bottom: 0;
}
</style>
</head>
<body>
<div class="container">
<ul id="js-news" class="js-hidden"></ul>
</div>
<footer>
<div class="container" id="newswiretxt">
<span></span>
</div>
</footer>
</body>
<script type="text/javascript">
var newsWire = [];
function loadNewswire() {
$.getJSON('http://api.nytimes.com/svc/news/v3/content/all/all.json',
{'api-key': 'XXXXXXXXX'},
function(data) {
console.log(data)
var newsWireTemp = [];
for (var i = 0; i < data.results.length; i++) {
var breakingNews = data.results[i];
var breakingTitle = breakingNews.title;
var breakingAbstract = breakingNews.abstract;
newsWireTemp.push(breakingTitle);
newsWireTemp.push(breakingAbstract);
}
newsWire = newsWireTemp;
console.log(newsWire.length);
});
}
loadNewswire();
console.log(newsWire.length);
$(document).ready(function() {
var items = newsWire;
$text = $('#newswiretxt span'),
delay = 10; //seconds
function loop (delay) {
$.each(items, function (i, elm){
$text.delay(delay*1E3).fadeOut();
$text.queue(function(){
$text.html(items[i]);
$text.dequeue();
});
$text.fadeIn();
$text.queue(function(){
if (i == items.length -1) {
loop(delay);
}
$text.dequeue();
});
});
}
loop(delay);
});
</script>

The main thing is this:
...
loadNewswire();
console.log(newsWire.length);
...
When you call loadNewsWire, you're starting an asynchronous JSON request. However, the script execution won't wait for that function to complete, so it immediately runs the following console.log statement. At that point, the JSON request hasn't completed, so the newsWire array is still empty - which is why console.log(newsWire.length) returns 0 there.
Inside your loadNewsWire function, you have a callback function that gets executed when the JSON request has returned your data. At this point you're populating the array, and console.log(newsWire.length) gives you the expected count.
Update in response to comment:
Is there anyway to make the rest of my code wait for the function to
execute?
Yes! $.getJSON is a convenience wrapper for $.ajax, which returns a jqXHR object (full juicy details in the jQuery documentation). You can add additional callbacks to that object, which is what you're actually doing inline in your call to $.getJSON. The following:
$.getJSON('http://...', { 'api-key': '...' },
function (data) {
// stuff
});
Is equivalent to:
$.getJSON('http://...', { 'api-key': '...' })
.done(function (data) {
// stuff
});
So, if you modify your loadNewswire to return the object returned from $.getJSON, you can attach a callback to it that will wait for the async operation to complete, and place the rest of your code inside that. If you change your code to this:
function loadNewswire() {
return $.getJSON(
...
);
};
You can then wrap the code you want to wait using one of the done, fail or always callbacks.
Your calling code would then look like this:
loadNewswire().done(function () {
console.log(newsWire.length);
// and any other code you want to hold until the async operation is complete
});
I'd suggest reading through the previously mentioned documentation - it's a bit heavy, but it gives a good overview of how to work with async requests using jQuery.

Related

javascript & api calls: how to get value returned from an api call? [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
For example, if I define code like this
var price;
$.getJSON('https://api.coindesk.com/v1/bpi/historical/close.json?start=2013-09-01&end=2013-09-05', function( data ) {
price = data.bpi;
});
then I can get price from console;
However, if I define code like this
var price;
$.getJSON('https://api.coindesk.com/v1/bpi/historical/close.json?start=2013-09-01&end=2013-09-05', function( data ) {
price = data.bpi;
});
console.log(price);
the price can still be accessed from console, but console.log returns undefine.
My question is that how can I get the price returned from data.api, so I can latter use the data to do some further calculation, such as
var x = Object.keys(price);
var y = Object.values(price);
// some plot using x, y, and some calculations
The get method is asynchronous meaning that the code execution proceeds to the statement after the get block without waiting for it. Once the get call completes, the code inside the get block is executed with the result returned. Thus, the console log statement is executed even before the call has finished and is undefined.
If you want to process the data returned, all calculations have to be done inside the callback method of the get() function.
$.getJSON('https://api.coindesk.com/v1/bpi/historical/close.json?start=2013-09-01&end=2013-09-05', function( data ) {
price = data.bpi;
var x = Object.keys(price);
var y = Object.values(price);
});
I think javascript promises are what you're looking for.
const promise = new Promise((resolve, reject) => {
const url = 'https://api.coindesk.com/v1/bpi/historical/close.json?start=2013-09-01&end=2013-09-05';
$.getJSON(url, data => {
resolve(data);
});
});
//and then later
promise.then(data => {
console.log(data.bpi);
});
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
</head>
<body>
</body>
</html>
price = data.bpi; can't run right away but your code continues to execute so console.log(price); runs before price = data.bpi;.
Also when you want to use the data later you might have to wait for it to be available. That's what the promise is doing.

Checking value of returned JSON and updating if false

I have a function that fetches questions from an external API. I need to have at least 5 questions in a array for my application to function.
function getCategoryQuestions(){
var questions = {};
var i = 0;
for (i; i < 6; i++){
$.getJSON(makeURL(), function(data){
data = data.category;
questions[data.name] = data.questions;
validQuestionsLength(questions, data.questions)
})
}
return questions;
}
I run validQuestionsLength to check if each questions array is at least 5 elements in length.
function validQuestionsLength(hash, array){
while (array.length < 5) {
$.getJSON(makeURL(), function(data){
data = data.category;
hash[data.name] = data.questions
array = data.questions;
})
console.log(array.length)
}
}
I want to check if an array's length is less than 5 and if so I'll fetch a new resource from the url. I'll save the new value to array and then it will check this new value's array length. I want it to keep fetching data until the condition is true.
What's happening is that once the validQuestionsLength() method runs and I have an array length with less than 5, it keeps console.logging the current array in an infinite loop. I thought I was saving a new resource to array on each loop.
I'm not sure where my logic error is happening.
I know there is an asynchronous request occurring so the console.log may run before the array variable is reset. I edited my validation function to include a done callback to execute once the async has finished
function validQuestionsLength(hash, array){
if (array.length < 5) {
$.getJSON(makeURL(), function(data){
data = data.category;
hash[data.name] = data.questions
console.log("data is ", data.questions)
array = data.questions;
}).done(console.log("array is ", array ))
}
}
My intention was to to save the newly fetched data to array. But data.questions and the array (that should point to the value for data.questions) are different values. What is .done() doing in this case?
Edit2: I made example how your two functions may look as valid callback loop, both functions are similar so it can be just one function:
I have simplified my script and for tests I made my testResponse php script returns 4 elements ["1", "2", "3", "4"] array few times and finally it returns 5 elements ["1", "2", "3", "4", "5"] array.
function getCategoryQuestions(callback){
$.getJSON('http://localhost/testResponse.php', function(data){
if(data.questions.length < 5){
getCategoryQuestions(callback);
} else {
callback(data.questions);
}
});
}
The code will get json from url, if the questions array is less then 5 it will request the url again until there will be 5 or more questions in array.
If there will be 5 or more questions in array it won't execute itself again, it will call callback with questions array as an argument.
This is how you use the function and receiver your questions array:
getCategoryQuestions(function(questions){
console.log(questions);
});
There is more to be done, like ajax error handling.
Edit: getJSON is async funciton so it will call its callback function after it will fetch the data, so the callback will be executed later anyway, even if it is not added to event queue.
It is because getJSON success callback function execution is added to event queue, and it executes after you console.log your array.length
I made a test:
<html>
<head>
<script
src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
</head>
<body>
<script>
$.getJSON('http://localhost/testResponse.php', function(data){
console.log('Callback');
});
console.log('Script end');
</script>
Hello!
</body>
</html>
And the result in console is:
Script end
Callback
To explain further how it happens I will provide two examples
1st example:
function callCallback(callback){
callback();
}
callCallback(function(){
console.log('Callback');
});
console.log('Script end');
Will produce:
Callback
Script end
And the second one:
function callCallback(callback){
setTimeout(callback, 0);
}
callCallback(function(){
console.log('Callback');
});
console.log('Script end');
Output will be same as my getJSON example, so:
Script end
Callback
It is because even thare is zero timeout, callback function is added to event queue, so it is not executed immediately.
Edit: Also getJSON is async funciton so it will call its callback function after it will fetch the data, so the callback will be executed later anyway, even if it is not added to event queue.

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 :)

Execute dynamic number of ajax request sequentially

Have the following scenario :
I have to display the graphs for a given interval (startDate,endDate)
Because the interval might be quite big , the data is retrieved per day so I need
to do multiple ajax calls sequentially and to append the data to the graph(highcharts)
Example interval is n days ==>
ajax request day 1
when is (done) ready ajax request day 2
when is (done) ready ajax request day 3
....
ajax request day n
I read about deferred and promises BUT I found difficult to with dynamic number of days and the requirement to get the responses sequentially
Thanks
If you're able to store the list of dates in an array, you can use something like this:
var items = ['Apple', 'Orange', 'Banana', 'Alphalpha'];
//replaceable with any function that returns a promise
function asyncFunction(item) {
return $.ajax({
url: '/echo/html',
type: 'POST',
data : item
})
.then(function(data){
$('body').append('<div>Got the response from '+item+'</div>');
//stuff stuff stuff
});
}
function sequence(arr, callback) {
var i=0;
var request = function(item) {
return callback(item).then(function(){
if (i < arr.length-1)
return request(arr[++i]);
});
}
return request(arr[i]);
}
sequence(items, asyncFunction).then(function(){
$('body').append('<div>Done with all!</div>');
});
https://jsfiddle.net/7ojy9jnx/2/
Basically, sequence takes an Array of items and runs a function on all of them (in this case asyncFunctions, which can be replaced with any function), a function that returns a promise.
This is very basic implementation, you'll notice, for example, it has no error handling. Libraries like async.js have an exhaustive list of tools that accomplish tasks like this, but who knows, maybe this will suffice.
Not sure if you already figured it out, but a good way to tackle your problem would be using a combination of jQuery.Deferred and recursion. Check out this sample code and see if it helps clarify things:
function getData(dayLimit) {
var allDone = $.Deferred();
var getDataForDay = function(day) {
doAsyncThing(day).done(function() {
if (day < dayLimit) {
getDataForDay(day + 1);
} else {
allDone.resolve();
}
}).fail(function(){
/*
Reject the deferred if one of your operations fails.
Useful if you're binding "fail" or "always" callbacks
to the promise returned by getData.
*/
allDone.reject();
});
};
getDataForDay(1); //start with first day
return allDone.promise();
}
Let me know if you need more clarification, happy to help!
What about recursively calling. Create a parameterized function and pass the day to the function like,
function getDetails(day) {
// ajax call
// In the callbacks call the getDetails function by updating the date
}
If you are using Jquery in your Application try pushing all the ajax to an array
ex
[ajax,ajax,...]
and then user
$.when([ajax,ajax,...]).then(function(){
console.log(arguments);// you will get the success messages in arguments array
})

Using $.getJSON on a global variable doesn't seem to work

I'm extremely new to Javascript and jQuery and I'm not quite understanding why the following code doesn't work
var collectibleCards = [];
$(document).ready(function () {
$.getJSON('AllSets.json', function (json) {
$.each(json, function(sets, cards) {
$.each(cards, function(element, card) {
if(card['collectible'] && card['type'] !== "Hero") {
collectibleCards.push(new Card(card));
}
});
});
});
});
console.log(collectibleCards.length); // prints 0
Why does collectibleCards not get any elements added? I've even tried just pushing numbers and still get nothing added to the array.
It's because getJSON is async operation and result of the callback will appear after some time, when browser get response from the server (or, in your case, from a json file).
So lets see:
// You create a variable
var collectibleCards = [];
// You start your ajax request by doing getJSON
$.getJson('AllSets.json', function () { // This code will evaluate AFTER browser get response from the file });
// You logged your variable and it still an emtpy array because ajax doesn't get a response yet
console.log(collectibleCards.length); // prints 0
You are trying to access the variable before its getting updated in success callback of getJSON, thats why you are getting it of length 0, If you want to access it outside of callback then use $.ajax and make it synchronous call else manipulate on collectibleCards in callback function itself not outside.
var collectibleCards = [];
$(document).ready(function () {
$.getJSON('AllSets.json', function (json) {
$.each(json, function(sets, cards) {
$.each(cards, function(element, card) {
if(card['collectible'] && card['type'] !== "Hero") {
collectibleCards.push(new Card(card));
}
});
});
console.log(collectibleCards.length); // print correct length
});
});
Also see
Calling Ajax and returning response

Categories

Resources