How to set object variable from async callback - javascript

I just started with creating custom objects in javascript. I want to set this.someObjVar using code below, but something wrong in my approach. Maybe async response uses it's own scope or thread.
// The code below used like:
someClass = new extfoo.SomeClass();
someClass.loadArrFromFile();
// this will be called far later after async returns
someClass.showSomeObjVar();
extfoo.js
=========
var extfoo = {};
extfoo.SomeClass = function() {
this.someObjVar = [];
this.showSomeObjVar = extfoo.showSomeObjVar;
this.loadArrFromFile = extfoo.loadArrFromFile;
};
// Bad results here
extfoo.showSomeObjVar = function() {
// results '0'
console.log('showSomeObjVar: ' + this.someObjVar.length);
};
// Async array population
extfoo.loadArrFromFile = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
this.someObjVar = xhr.responseText.split('\r\n')
// results '23' elements
console.log("someObjVar length: "+this.someObjVar.length);
}
}
// request code ...
};

Are you certain that this.someObjVar is referencing extfoo?
There's a chance that the this object in the extfoo.loadArrFromFile function is referencing the function itself (xhr.onreadystatechange), and not the extfoo object:
extfoo.loadArrFromFile = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
//I'm going to create a new variable for this function called this.someObjVar
//Cause I don't know about extfoo's.
this.someObjVar = xhr.responseText.split('\r\n')
// results '23' elements
console.log("someObjVar length: "+this.someObjVar.length);
//Whup, end of the line, I guess I'll just forget about that variable I just made.
}
}
// request code ...
};
There are several ways to fix this, but to test, try changing this in the extfoo.loadArrFromFile function to extfoo.
Here's how to fix it:
extfoo.loadArrFromFile = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
//OOOH you mean that object
extfoo.someObjVar = xhr.responseText.split('\r\n')
// results '23' elements
console.log("someObjVar length: "+extfoo.someObjVar.length);
}
}
// request code ...
};
One last thing... is extfoo.SomeClass.someObjVar supposed to be different from extfoo.someObjVar? This code will work if that's the case, but otherwise, you'll want to include a SomeClass in between each reference.

Well since it's an asynchronous request, someClass.loadArrFromFile() will not wait until the request is complete, so someClass.showSomeObjVar() will be called before it's complete.
You could add a termination handler to handle the results:
extfoo.loadArrFromFile = function(done) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
this.someObjVar = xhr.responseText.split('\r\n')
// results '23' elements
console.log("someObjVar length: "+this.someObjVar.length);
done();
}
}
You could then do something like this:
someClass.loadArrFromFile(function() {
someClass.showSomeObjVar();
});

Related

Can not make XMLHttpRequest to 3 separate API urls

Before I start, here is the API. Super simple: https://www.cryptonator.com/api
To note, I have worked with api before, but i used a await async function, but for some reason I couldn't get that to work....but I found a tutorial for doing this with XML http request, so I decided to just move forwards doing it in XML because I was able to make a simple Bitcoin ticker.
I am building a simple widget to display the prices of Bitcoin, Litecoin, and Ethereum using the cryptonator API. Like I said above, I made a bitcoin ticker with the first function ( see code below ), and it works great. However, I am having issues trying to get 3 different currencies.
Here is what I am trying to do.
var url = "https://api.cryptonator.com/api/ticker/btc-usd";
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var json = JSON.parse(this.responseText);
parseJson(json);
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
function parseJson(json) {
var usdValue = json["ticker"]["price"];
document.getElementById("data").innerHTML = usdValue;
var usdValue = usdValue.replace(/[^\d.\-]/g, "");
var usd = parseFloat(usdValue);
document.getElementById("data").innerHTML = "$ " + usd.toFixed(2);
}
//
//
var xmlhttp2 = new XMLHttpRequest();
var url2 = "https://api.cryptonator.com/api/ticker/ltc-usd";
xmlhttp2.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var json = JSON.parse(this.responseText);
parseJson(json);
}
};
xmlhttp2.open("GET", url2, true);
xmlhttp2.send();
function parseJson(json) {
var LTCusdValue = json["ticker"]["price"];
// document.getElementById("data2").innerHTML = LTCusdValue;
var LTCusdValue = LTCusdValue.replace(/[^\d.\-]/g, "");
var LTCusd = parseFloat(LTCusdValue);
document.getElementById("data2").innerHTML = "$ " + LTCusd.toFixed(2);
}
//
//
//
var xmlhttp3 = new XMLHttpRequest();
var url3 = "https://api.cryptonator.com/api/ticker/eth-usd";
xmlhttp3.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var json = JSON.parse(this.responseText);
parseJson(json);
}
};
xmlhttp3.open("GET", url3, true);
xmlhttp3.send();
function parseJson(json) {
var ETHusdValue = json["ticker"]["price"];
// document.getElementById("data3").innerHTML = ETHusdValue;
var ETHusdValue = ETHusdValue.replace(/[^\d.\-]/g, "");
var ETHusd = parseFloat(ETHusdValue);
document.getElementById("data3").innerHTML = "$ " + ETHusd.toFixed(2);
}
As you can see, I am trying to make 3 request to 3 different APis, but it isn't working. If I comment out all but one of these functions, it works fine. My issues comes when i try to use all 3 at once. If i use Bitcoin and Litecoin only, it will actually work, but will just break again once I try to use the 3rd function ( to get ethereum price ).
The parseJson function is defined three times. This means that every time you write the function it will overwrite the previous definition. So in your case only the last parseJson function will be used for all three requests. You could do a couple of things.
Write three different variations. (Bad practice)
Though this would be the less favorable of the options. It will require you to have repeated code copied multiple times. This can be done more efficiently.
function parseJson1(json) {
...
}
function parseJson2(json) {
...
}
function parseJson3(json) {
...
}
Add an argument to the function. (Good practice)
Give the parseJson function a second argument that selects the element to output the value. This should be the id of the element you'll want to select.
This is the better solution because it only requires you to write a function once and call it multiple times accounting for the variations.
function parseJson(json, id) {
var output = document.getElementById(id); // Select the element based on the id.
if (output === null) { // If element is not found, stop the function.
return;
}
var price = json["ticker"]["price"];
var usdValue = price.replace(/[^\d.\-]/g, "");
var usd = parseFloat(usdValue);
output.innerHTML = "$ " + usd.toFixed(2);
}
The last technique is applicable to the rest of your code. Be aware of repeating yourself. You'll write much cleaner and better code when you only have to write something once.
If I understood you well you can create a method for all cryptos and avoid repeting the same code. If you run the example below you will be able to see all cryptos and also it's easy to add new ones:
const URL = 'https://api.cryptonator.com/api/ticker/'
const cryptos = ['btc', 'ltc', 'eth']
cryptos.map(crypto => {
fetch(`${URL}${crypto}-usd`)
.then(data => data.json())
.then(({ ticker: { base, price } }) => console.log(`${base}: ${(+price).toFixed(2)}`))
})
We are using fetch that is modern XHR. Hope this help.
You should be able to write an async wrapper function for this using a Promise.. This will allow you to use async/await with XHR..
To make using XHR a lot easier, you can use the built in fetch API.. According to this, most browsers support fetch.. All in all, I prefer using fetch over axios, XHR, etc.. but since you specifically asked about XHR, that is how I answered.
If you did not want to await each response, you can do something like this:
// basic XHR wrapper function for 'get'
function getXHR(url) {
return new Promise(function(resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
this.status == 200
? resolve(this.responseText)
: reject(this.status);
}
};
xhr.send();
});
}
const ethElement = document.getElementById("eth");
const bcElement = document.getElementById("bc");
const lcElement = document.getElementById("lc");
// etherium
getXHR("https://api.cryptonator.com/api/ticker/eth-usd")
.then(eth => {
// Can turn into JSON like this:
//const ethJson = JSON.parse(eth);
ethElement.innerHTML = eth;
})
// bitcoin
getXHR("https://api.cryptonator.com/api/ticker/btc-usd")
.then(bc => {
// Can turn into JSON like this:
//const bcJson = JSON.parse(bc);
bcElement.innerHTML = bc;
})
// litecoin
getXHR("https://api.cryptonator.com/api/ticker/ltc-usd")
.then(lc => {
// Can turn into JSON like this:
//const lcJson = JSON.parse(lc);
lcElement.innerHTML = lc;
})
<h1>eth:</h1>
<pre id="eth"></pre>
<h1>bc:</h1>
<pre id="bc"></pre>
<h1>litecoin:</h1>
<pre id="lc"></pre>
I have created the following demo to show you how to accomplish this using async/await:
Init(); // call 'main' function
// basic XHR wrapper function for 'get'
function getXHR(url) {
return new Promise(function(resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
this.status == 200
? resolve(this.responseText)
: reject(this.status);
}
};
xhr.send();
});
}
// "Main" function
async function Init() {
const ethElement = document.getElementById("eth");
const bcElement = document.getElementById("bc");
const lcElement = document.getElementById("lc");
// etherium
const eth = await getXHR("https://api.cryptonator.com/api/ticker/eth-usd");
const ethJson = JSON.parse(eth);
ethElement.innerHTML = ethJson.ticker.price + " " + ethJson.ticker.target;
// bitcoin
const bc = await getXHR("https://api.cryptonator.com/api/ticker/btc-usd");
const bcJson = JSON.parse(bc);
bcElement.innerHTML = bcJson.ticker.price + " " + bcJson.ticker.target;
// litecoin
const lc = await getXHR("https://api.cryptonator.com/api/ticker/ltc-usd");
const lcJson = JSON.parse(lc);
lcElement.innerHTML = lcJson.ticker.price + " " + lcJson.ticker.target;
}
div h1,
div p {
display: inline;
vertical-align: top;
font-family: 'Open Sans', sans-serif;
line-height: 38px;
}
<div>
<h1>eth:</h1>
<p id="eth"></p>
</div>
<div>
<h1>btc:</h1>
<p id="bc"></p>
</div>
<div>
<h1>ltc:</h1>
<p id="lc"></p>
</div>

Cannot access array element. use result of one ajax request to another ajax request

//global variables
var corpArray = new Array(); //store corp classes instances
var corpId = new Array(); //store corp id's
window.onload = init();
function init() {
getNpcCorpId();
console.log(corpId);
getCorpNames(corpId[5]);
}
//get all corporation id's from the game
function getNpcCorpId() {
let conName = new XMLHttpRequest();
conName.onload = function() {
if (this.status == 200) {
let idList = JSON.parse(this.responseText);
idList.forEach(element => {
corpId.push(element);
});
}
};
conName.open(
"get",
"https://esi.evetech.net/latest/corporations/npccorps/?datasource=tranquility",
true
);
conName.send();
}
//get corporation name
function getCorpNames(element) {
console.log(element);
let corpConn = new XMLHttpRequest();
corpConn.onload = () => {
if (this.status == 200) {
console.log(this.responseText);
}
};
corpConn.open(
"get",
`https://esi.evetech.net/latest/corporations/${element}/?datasource=tranquility`,
true
);
corpConn.send();
}
I am trying to create an eve online api, i want to use 2 global varibles to store my retrieved values (because i do not know another way) i will use a couple of functions to use the provieded eve api.I can not acces my corpId individual elements, when i console log all my array ,everything is ok,but when i want to access individual element it appears undefiend.
//global variables
var corpArray = new Array(); //store corp classes instances
var corpId = new Array(); //store corp id's
window.onload = init();
async function init() {
await getNpcCorpId();
console.log(corpId);
getCorpNames(corpId[5]); // asynchronous behaviour - let it be fix using await and async
}
//get all corporation id's from the game
function getNpcCorpId() {
return new Promise(function(resolve, reject) {
let conName = new XMLHttpRequest();
conName.onload = function() {
if (this.status == 200) {
let idList = JSON.parse(this.responseText);
idList.forEach(element => {
corpId.push(element);
});
}
resolve();
};
conName.open(
"get",
"https://esi.evetech.net/latest/corporations/npccorps/?datasource=tranquility",
true
);
conName.send();
});
}
//get corporation name
function getCorpNames(element) {
console.log(element);
let corpConn = new XMLHttpRequest();
corpConn.onload = () => {
if (this.status == 200) {
console.log(this.responseText);
}
};
corpConn.open(
"get",
`https://esi.evetech.net/latest/corporations/${element}/?datasource=tranquility`,
true
);
corpConn.send();
}
This is due to asynchronous behaviour - in simple terms - when you
call getNpcCorpId() it is a http request it will take some time to
execute but next line run immediately so at that point of time corpId
is still blank. So to fix this issue you can use await that will makes
JavaScript wait until the promise returns a result.
Hope this helps you !

Asynchronous Ajax Call Mixing Up Callbacks

So basically I have an ajax function pretty standard one. Like so:
function ajax_call(rest_req, url, success_callback, fail_callback) {
// if (request_in_progress)
// return;
// request_in_progress = true;
var xhttp = new XMLHttpRequest;
xhttp.onreadystatechange = function () {
if (this.readyState == 4) {
// request_in_progress = false;
if (this.status == 200) {
success_callback(this);
}
else {
fail_callback(this);
}
}
};
xhttp.open(rest_req, url, true);
xhttp.send();
}
When I use the ajax function this way:
(function() {
function setup() {
ajax_call("GET", "url1", function(xhttp) {
response = JSON.parse(xhttp.responseText);
if (response["error"] != 100)
document.getElementById('url1-reading').innerHTML = '---';
else
document.getElementById('url1-reading').innerText = response["response"];
},
function() {}
);
ajax_call("GET", "url2" , function(xhttp) {
response = JSON.parse(xhttp.responseText);
if (response["error"] != 100)
document.getElementById('url2-reading').innerHTML = '---';
else
document.getElementById('url2-reading').innerText = response["response"];
},
function() {}
);
console.log('Refresh');
}
setInterval(setup, 1000);
})();
This code behaves differently than what I expect. When I run this code, there are some times when the results that were suppose to go to url1 success_callback goes inside url2's success_callback.
To put another way the response variable inside url1 ajax_call is what I expected to show up as response variable for url2. So in effect the ajax_call seem to not know what success_callback is for what even though I explicitly pass it in as a parameter.
I'm coming from a C++ background so this is a difficult concept to grasp. How do I do this the right way? I hope my question is clear. Please tell me what is not clear so I can clarify.
The way you declare it, response is a global variable. Try changing response = to let response =

JSON parse error: Cannot read property

I have created some little jt code, but it gives me error
function Mind(){
var request = "request";
var reply = "reply";
var words = '';
this.Reply = function(){
if(request == words.nouns[0].noun){
reply = words.nouns[0].noun;
}
else
reply = this.words.nouns[0].noun;
}
this.SetRequest = function(req){
request = req;
}
this.GetReply = function(){
return reply;
}
this.Parse = function(u){
var xmlhttp = new XMLHttpRequest();
var url = u;
var result;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
words = JSON.parse(xmlhttp.responseText);
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
return result;
}
this.Construct = function(){
words = this.Parse('mind/words.json');
}}
var mind = new Mind();
mind.Parse('mind/words.json');
and here is my json file
{
"nouns": [
{"noun": "child"},
{"noun": "father"}
]
}
In command live all goes well, but when I run this code, appears error
Uncaught TypeError: Cannot read property 'nouns' of undefined
Mutliple errors. The most fundamental one is that your code ignores that XMLHttpRequest is async, and wont return a value in the same way as "regular" functions. Read about it here: How to make a function wait until a callback has been called using node.js. The TL;DR is that you have to pass in a "callback-function" to your parse-method and "return" your value using that function, instead of using a return-statement. Google for "javascript callbacks" and read a few tutorials if this concept is new to you!
You also have some minor errors, like returning result from Parse, but never actually setting result to anything. Also words is being assigned in multiple places in a way that doesn't really make sense. But both of these things will go away when you solve the sync/async issues.
EDIT:
Essentially the fix looks like this:
this.Parse = function(u, callback){ // this "callback" is new
var xmlhttp = new XMLHttpRequest();
var url = u;
var result;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
words = JSON.parse(xmlhttp.responseText);
callback(null, words); // we're "returning" the words here
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
// no return statement here!
}
this.Construct = function(){
this.Parse('mind/words.json', function(error, words) {
// here you can use your words!
});
}}

why i can't parse xml in javascript?

hello i have problem to parse xml..
i have xml like this :
<tejemahan>
<kategori> komputer </kategori>
<hasil> aplikasi komputer </hasil>
</terjemahan>
Edited:
xml above I get in that way :
var url="http://localhost:8080/inlinetrans/api/translate/"+userSelection+"/"+hasilStemSel+"/"+hasilStem;
var client = new XMLHttpRequest();
client.open("GET", url, false);
client.setRequestHeader("Content-Type", "text/plain");
client.send(null);
if(client.status == 200)
alert("the request success"+client.responseText);
else
alert("the request isn't success"+client.status+""+client.statusText)
}
and this is my code to parse an xml file above :
this.loadXML = function (){
var url = http://localhost:8080/coba/api/artikan/"+sel+"/"+hasilStemSel+"/"+hasilStem
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.load("url");
xmlDoc.onload= this.readXML;
}
this.readXML = function() {
alert(xmlDoc.documentElement.tagName);
alert(xmlDoc.documentElement.childNodes[0].tagName);
alert(xmlDoc.documentElement.childNodes[1].tagName);
alert(xmlDoc.documentElement.childNodes[0].textContent);
alert(xmlDoc.documentElement.childNodes[1].textContent);
}
i can execute this code
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.load("url");
but why i can't execute this code
xmlDoc.load = this.readXML ???
Try putting the onload handler assignment before the load() call. If you call load() first, the onload event will happen before you have assigned a handler to handle it. Like this:
xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.onload= this.readXML;
xmlDoc.load("url");
Firstly, I second David Dorward's suggestion: use XMLHttpRequest instead, which will work in all major browsers. Code is below.
Secondly, your readXML function is flawed, since most browsers will include whitespace text nodes within the childNodes collection, so xmlDoc.documentElement.childNodes[0] will actually be a text node and have no tagName property. I would suggest using getElementsByTagName() or checking the nodeType property of each node as you iterate over childNodes.
Thirdly, your XML is not valid: the <tejemahan> and </terjemahan> do not match, although this may be a typo in your question.
var url = "http://localhost:8080/coba/api/artikan/"+sel+"/"+hasilStemSel+"/"+hasilStem;
var readXML = function(xmlDoc) {
alert(xmlDoc.documentElement.tagName);
var kategori = xmlDoc.getElementsByTagName("kategori")[0];
alert(kategori.tagName);
};
var createXmlHttpRequest = (function() {
var factories = [
function() { return new XMLHttpRequest(); },
function() { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); },
function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); },
function() { return new ActiveXObject("Microsoft.XMLHTTP"); }
];
for (var i = 0, len = factories.length; i < len; ++i) {
try {
if ( factories[i]() ) {
return factories[i];
}
}
catch (e) {}
}
})();
var xmlHttp = createXmlHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
readXML(xmlHttp.responseXML);
}
};
xmlHttp.open("GET", url, true);
xmlHttp.send(null);

Categories