Confusion over Javascript callback functions / promises [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I'm still quite a noob at Javascript and one thing that I'm struggling to wrap my head round is forcing synchronicity into a primarily asynchronous programming language.
I have a function that loads a decent size JSON file and another function that utilises the return of this function.
I have read a lot of other threads about similar issues and keep seeing very different solutions, I was just curious what the best way to handle this would be.
Constructor calling the two functions -
class Guild extends MapObject {
constructor(x,y,floor,type,name) {
super(x,y,floor);
this.levels = [];
this.type = type;
this.name = name;
this.guildSelector();
this.addLevels(this.name);
this.setTip();
}
guildSelector function that loads the JSON and returns it as this.guildData
guildSelector(){
var oReq = new XMLHttpRequest();
this.guildData = null;
oReq.onload = reqListener;
oReq.open("get", "/json/guilds.json", true);
oReq.send();
function reqListener() {
this.guildData = JSON.parse(this.responseText);
console.log(this.guildData);
return this.guildData;
}
}
addLevels function that currently runs before this.guildData is populated with the JSON causing it to crash as it can't find .guilds of null.
addLevels(name) {
if(name == "default"){
this.name = "lowerSchoolOfEndurance";
}
console.log(this.guildData);
this.guildData.guilds.forEach(function(guild) {
console.log(guild.name);
if(guild.name == name){
for(var i = 1; i <= guild.levels.length; i++){
var guildStats = guild.levels[i-1];
for (var k in guildStats) {
console.log("Level "+i+" of the guild "+name+" increases "+k+" stat by "+guildStats[k]);
this.levels.push(guild.levels[i-1]);
}
}
}
});
return this.levels;
}
So the TL:DR would essentially be, what is the best way to not trigger my class that requires the JSON to be loaded until the JSON is loaded.
Kind Regards,
Earl Lemongrab

You should use Promises:
guildSelector(){
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
this.guildData = null;
oReq.onload = reqListener;
oReq.open("get", "/json/guilds.json", true);
oReq.send();
function reqListener() {
this.guildData = JSON.parse(this.responseText);
resolve(this.guildData);
}
})
}
Then change your constructor to utilize the Promise:
this.guildSelector().then((guildData) => {
this.guildData = guildData;
this.addLevels(this.name);
});

Related

How to get value from async method (ajax) [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 9 months ago.
I am not used to asynchronous method so I would like to have your help. I have this ajax method that gets the data of my mysql tables. I need to retrieve this data to use it in my code.
Here is my code :
function ajaxRequest(type,url,callback,data=""){
let request = new XMLHttpRequest();
request.open(type, url);
request.onload = function () {
switch (request.status){
case 200:
case 201: //console.log(request.responseText);
callback(request.responseText);
break;
default: console.log(request.status);
}
};
request.send(data);
}
function setAvailableQuestions(){
ajaxRequest("GET","../controller.php?func=get_availableQuestion", (questions) => {
var allQuestions = JSON.parse(questions);
return allQuestions;
});
}
function getNewQuestion(){
var availableQuestions = setAvailableQuestions();
console.log(availableQuestions); //undefined
}
I read that you could solve it in two different ways, first one is to put ajax request asynchronous to false :
function setAvailableQuestions(){
var ajax = new XMLHttpRequest();
var asynchronous = false;
ajax.open("GET","../controller.php?func=get_availableQuestion", asynchronous);
ajax.send();
ajax.onreadystatechange = function(){
var allQuestions = JSON.parse(this.response);
return allQuestions;
}
}
function getNewQuestion(){
var availableQuestions = setAvailableQuestions();
console.log(availableQuestions); //undefined
}
But it still displays undefined
And the other method is to use .then or await for asynchronous methods but I don't know how to apply it to my first code.
Does anyone has an idea to help me ?
Try setting the "return" on setAvailableQuestions().
function setAvailableQuestions(){
var ajax = new XMLHttpRequest();
var asynchronous = false;
ajax.open("GET","../controller.php?func=get_availableQuestion", asynchronous);
ajax.send();
ajax.onreadystatechange = function(){
var allQuestions = JSON.parse(this.response);
return allQuestions;
}
return ajax.onreadystatechange;
}

Best method to save an XMLHttpRequest() to variable [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
What is the best method to save a XMLHttpRequest to a variable to use in a if statement. I'm trying to get the image size and depending on the size do something.
** *Please note that I'm using a random image from the internet for this question. I'm currently testing using a local server * **
This is what I have tried so far:
function get_image_size(imgurl) {
var xhr = new XMLHttpRequest();
let x
xhr.open('HEAD', imgurl, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
x = xhr.getResponseHeader('Content-Length');
console.log(xhr.getResponseHeader('Content-Length')) // This works! returns bytes
} else {
console.log('ERROR');
}
}
};
xhr.send(null);
console.log(x) // This doesn't work
return x // This doesnt return anything
}
let image_size = get_image_size("https://images.ctfassets.net/hrltx12pl8hq/7yQR5uJhwEkRfjwMFJ7bUK/dc52a0913e8ff8b5c276177890eb0129/offset_comp_772626-opt.jpg?fit=fill&w=800&h=300")
// image_size returns "undifined"
I've also tried:
function get_filesize(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open("HEAD", url, true);
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
callback(parseInt(xhr.getResponseHeader("Content-Length")));
}
};
xhr.send();
}
get_filesize("https://images.ctfassets.net/hrltx12pl8hq/7yQR5uJhwEkRfjwMFJ7bUK/dc52a0913e8ff8b5c276177890eb0129/offset_comp_772626-opt.jpg?fit=fill&w=800&h=300", function(size){image_size = size})
But still no luck.
You're making an asynchronous request, therefore, you can't have the value you need immediately assigned to x for use, your code needs a way to await the request before you can use or return x;
One approach is to wrap your get_image_size function in a Promise like this
function get_image_size(imgurl) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('HEAD', imgurl, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
resolve(xhr.getResponseHeader('Content-Length'))
} else {
console.log('ERROR');
}
}
};
xhr.send(null);
})
}
and use it like this
const IMAGE_URL = "https://images.ctfassets.net/hrltx12pl8hq/7yQR5uJhwEkRfjwMFJ7bUK/dc52a0913e8ff8b5c276177890eb0129/offset_comp_772626-opt.jpg?fit=fill&w=800&h=300";
get_image_size(IMAGE_URL).then((size) => {
console.log(size)
// YOU CAN ONLY USE SIZE HERE.
// NOT OUTSIDE OR REASSIGNED
// COS IT MAY NOT BE AVAILABLE AS THIS IS AN ASYNCHRONOUS OPERATION
})
You can only use the value of size inside the callback of the chained .then() method.
It will NOT work if you reassign or try to access it from outside, as it may not be available at the time your code executes.
Another approach is to use the callback function, which I see you have tried, but the problem is that you were reassigning it like this image_size = size.
You can only use it within that callback function.
So technically you need to wrap all the code that needs the image-size inside that callback.

Increment variable in constructor function (JS)

I am fetching the data from 2 different APIs and I wrote a constructor function that will start XMLHttpRequest based on the new instance of the object creation (if it makes any sense...). Once the data is fetched and parsed I want to save it into different named variables. For instance: trial1data, trial2data. At the minute the new instances of the objects overwrite the data variable. Code below:
var api = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&pretty=true";
var api2 = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&tel={phone|format}&address={streetAddress}&city={city}&state={usState|abbr}&zip={zip}&pretty=true";
function FetchData(apiType) {
var r = new XMLHttpRequest();
this.apiType = apiType;
this.request = function() {
r.open("GET", apiType, true);
r.onload = function() {
var data = JSON.parse(r.responseText);
}
r.send(null);
}
}
trial1 = new FetchData (api);
trial1.request();
trial2 = new FetchData (api2);
trial2.request();
Thanks for the XMLHttpRequest tip, but the issue was to save each data into separate variables such as trial1data and trial2data (or anything else that has got a meaning and I can re-use later on), based on how many new objects I will create.
Your var r = new XMLHttpRequest(); is common.
You need to move it inside the function to create separate request everytime the constructor function is called.
function FetchData(apiType) {
this.apiType = apiType;
this.request = function() {
var r = new XMLHttpRequest();
r.open("GET", apiType, true);
r.onload = function() {
var data = JSON.parse(r.responseText);
console.log(data);
}
r.send(null);
}
}
You should put the request object creation inside the constructor:
function FetchData(apiType) {
var r = new XMLHttpRequest();
this.apiType = apiType;
this.done = new Promise( res => {
r.onload = function() {
res(JSON.parse(r.responseText) );
};
});
this.request = function() {
r.open("GET", apiType, true);
r.send(null);
};
}
So you can do:
const req = new FetchData("whatever");
req.request();
req.done.then(console.log);
Create new instance on var r = new XMLHttpRequest(); inside of constructor, or as a better approach, make it an argument for a constructor and inject new XMLHttpRequets object for each.
To answer the second part of your question, you could store response data in object's property and access it directly or getter interface. So instead of
r.onload = function() {
var data = JSON.parse(r.responseText);
}
Do something like:
function FetchData(apiType) {
var self = this;
this.apiType = apiType;
this.request = function() {
var r = new XMLHttpRequest();
return new Promise(function(resolve, reject) {
r.open("GET", apiType, true);
r.onload = function() {
self.data = JSON.parse(r.responseText);
resolve(self.data);
}
r.send(null);
}
}
Then
trial1 = new FetchData (api);
var trial1resp;
trial1.request().then(function(data) {
trial1resp = data;
}
The last assignment is just to show how is response stored. You must handle async processess to achieve your goal.
You could read little bit more about promisses and how to handle xhr async tasks here https://developers.google.com/web/fundamentals/primers/promises

XMLHttpRequest() javascript

to resume my problem, i'm using many XMLHttpRequest() rockets, with a view to get the value (miniTable) returned by the TableRow() function. The problem is, with the alert() on the end of the TableRow() function, i'm have exactly the value that i want, but on TableContent2 variable i'm having an "Undefined" value. I don't know why!! here all the JS file that i'am using (don't care about variables and code calculating the variables). I really need your help, because i'm blocked since 3 days on that. Thank you again and good afternoon freinds.
(function() {
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
myFunction(xmlhttp);
}
};
xmlhttp.open("GET", "File1.xml", true);
xmlhttp.send();
})();
function ContentFunction(func) {
TableContent2 = TableRow();
alert(TableContent2);
}
function TableRow() {
xmlhttp3 = new XMLHttpRequest();
xmlhttp3.onreadystatechange = function() {
if (xmlhttp3.readyState == 4 && xmlhttp3.status == 200) {
texttest = myFunction2(xmlhttp3);
alert(miniTable);
return miniTable;
}
};
xmlhttp3.open("GET", "File2.xml", true);
xmlhttp3.send();
}
function myFunction2(xml) {
var xmlDoc2 = xml.responseXML;
var ObjectText;
var x = xmlDoc2.getElementsByTagName("Clip");
/*Calcule de ObjectText*/
alert(ObjectText);
return ObjectText;
}
function myFunction(xml) {
xmlhttp2 = new XMLHttpRequest();
var xmlDoc = xml.responseXML;
var x = xmlDoc.getElementsByTagName("Film");
xmlhttp2.onreadystatechange = function() {
if (xmlhttp2.readyState == 4 && xmlhttp2.status == 200) {
myFunction2(xmlhttp2);
}
};
xmlhttp2.open("GET", "File2.xml", true);
xmlhttp2.send();
}
TableRow returns nothing. The return statement at xmlhttp3.onreadystatechange isn't in the earlier scope. Besides that, your xmlhttp3 is set to be asynchronous, then you can't directly return any information of the AJAX. Synchronous requests, which are deprecated (that's why you shouldn't use them), can be directly read, since they act like a infinite loop that breaks when the request is done (for(;xhr.readyState!==4;);, doing this manually will pause the request and the script execution forever, this is why synchronous requests have been made before.).
Synchronous requests aren't a good idea, they break interaction with entire of the page, since they pause the page/script execution. For instance, if you've a animation, it'll be paused, including event listeners.
Also, it looks like miniTable haven't been declared in any part of your code.
Consider using callback functions, they'll be stored in the TableRow scope and can be called later, with extra arguments.
This is a base:
function ContentFunction(func) {
TableRow(function(TableContent2) {
alert(TableContent2);
});
}
function TableRow(doneFnc) {
var xmlhttp3 = new XMLHttpRequest;
xmlhttp3.onreadystatechange = function() {
if (xmlhttp3.readyState === 4 && xmlhttp3.status === 200) {
var texttest = myFunction2(xmlhttp3);
/* success callback */
doneFnc(texttest);
}
};
xmlhttp3.open("GET", "File2.xml", true);
xmlhttp3.send();
}

Strange javascript behavior - multiple active XMLHttpRequests at once? Long running scripts?

I'm attempting to issue two concurrent AJAX requests.
The first call (/ajax_test1.php) takes a very long time to execute (5 seconds or so).
The second call (/ajax_test2.php) takes a very short time to execute.
The behavior I'm seeing is that I /ajax_test2.php returns and the handler gets called (updateTwo()) with the contents from /ajax_test2.php.
Then, 5 seconds later, /ajax_test1.php returns and the handler gets called (updateOne()) with the contents from /ajax_test2.php still!!!
Why is this happening?
Code is here: http://208.81.124.11/~consolibyte/tmp/ajax.html
This line:-
req = new XMLHttpRequest();
should be:-
var req = new XMLHttpRequest();
As AnthonyWJones stated, your javascript is declaring the second AJAX object which first overwrites the req variable (which is assumed global since there is no var) and you are also overwriting the ajax variable.
You should separate your code i.e:
function doOnChange()
{
var ajax1 = new AJAX('ajax_test1.php', 'one', updateOne);
var ajax2 = new AJAX('ajax_test2.php', 'two', updateTwo);
}
function AJAX(url, action, handler)
{
if (typeof XMLHttpRequest == "undefined")
{
XMLHttpRequest = function()
{
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
throw new Error( "This browser does not support XMLHttpRequest." )
};
}
url = url + '?action=' + action + '&rand=' + Math.random()
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState == 4)
{
if (req.status == 200)
{
alert('' + handler.name + '("' + req.responseText + '") ')
handler(req.responseText)
}
}
}
req.open("GET", url, true);
req.send(null);
}
Regards
Gavin
Diodeus and Mike Robinson:
You guys didn't read my post fully. I know that one of the pages takes longer to execute than the other. That is the expected behavior of each page.
HOWEVER if you read my original post, the problem is that the callback for both pages ends up getting called with the HTML contents of the first page only.
AnthonyWJones and Gavin:
Thanks guys! That works like a charm! I guess I need to brush up on my Javascript!

Categories

Resources