Loading an array of XML files with asynchronous XMLHttpRequest - javascript

I want to load an array of XML files, and store them in an array.
Code example:
var src = [ "a", "b", "c", "d" ];
var dest = {};
for (var i in src) {
var req = new XMLHttpRequest();
req.open("GET", src[i], true);
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
dump(i + "\n");
dest[i] = req.responseXML;
}
}
}
req.send(null);
}
However, the dump result is always
3
3
3
3
It shows that the i referenced in callback is always the outer i, so the XML files cannot be stored correctly.
So, how to solve this issue? We have about 50 XML files to load and loading them one by one is not acceptable.
Thank you.

Don't use for..in to loop over arrays. Use a normal for loop.
You make the typical mistake of creating a function in a loop. JavaScript has no block scope, only function scope, so i will always refer to the last element of the array you looped over when the functions you created are executed. They all have a reference to the same i. You can solve this by using an immediately executing function that returns a function (thus, capturing the value of i).
If you do this, you also have to capture a reference to req, otherwise it will always refer to the last generated XMLHttpRequest (the same reason as for i).
So one solution would be:
var src = [ "a", "b", "c", "d" ];
var dest = {};
for (var i = src.length;i--;) {
var req = new XMLHttpRequest();
req.open("GET", xmlfile, true);
req.onreadystatechange = (function(i, req) {
return function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
dump(i + "\n");
dest[i] = req.responseXML;
}
}
};
}(i, req)); // capturing the current value/reference of i and req
req.send(null);
}
#Spiny Norman's solutions might be more readable ;)

You could do something like this:
var src = [ "a", "b", "c", "d" ];
var dest = {};
var loadXml = function(i) {
var req = new XMLHttpRequest();
req.open("GET", xmlfile, true);
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
dump(i + "\n");
dest[i] = req.responseXML;
}
}
}
req.send(null);
};
for (var x = 0; x < src.length; x++) {
loadXml(x);
}
By the way, it seems you're always loading the same xmlfile, but I'm sure this is different in your actual code ;)

Related

Because I only see the last element of the for loop

I'm having a problem; I have the following program code:
var xmlhttp = new XMLHttpRequest();
var url = "https://wjko5k6250.execute-api.us-east-2.amazonaws.com/motorcycle";
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var allmot = JSON.parse(this.responseText);
console.log(allmot);
for(var i = 0, len = allmot.Items.length; i < len; i++)
{
id=allmot.Items[i].id
var url1 = "https://wjko5k6250.execute-api.us-east-2.amazonaws.com/motorcycle/"+id;
console.log(url1);
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myArr = JSON.parse(this.responseText);
console.log(myArr);
document.getElementById("img").src = myArr.Item.image;
document.getElementById("brd").innerHTML = myArr.Item.brand;
}
};
xmlhttp.open("GET", url1, true);
xmlhttp.send();
}
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
allmot is as follows:
Items: Array (4)
0: {brand: 'Guzzi', id: '123456', image: 'moto_guzzi.jpg', date: '27/11/2021 '}
1: {brand: 'Bimota', id: '135623', image: 'bimota.jpg', date: '04/12/2021 '}
2: {brand: 'Ducati', id: '123789', image: 'b_desertx.jpg', date: ' 04/12/2021 '}
3: {brand: 'Benelli', id:' 146975 ', image:' benelli.jpg ', date: '27/11/2021'}
url1 returns (according to the for loop):
https://wjko5k6250.execute-api.us-east-2.amazonaws.com/articles/123456
https://wjko5k6250.execute-api.us-east-2.amazonaws.com/articles/135623
https://wjko5k6250.execute-api.us-east-2.amazonaws.com/articles/123789
https://wjko5k6250.execute-api.us-east-2.amazonaws.com/articles/146975
and so far everything seems to be fine.
The problem is in myArr; I noticed that it returns the image and brand of the last element only, so the one that has id equal to 146975.
Therefore there seems to be problems with the for loop.
Can anyone kindly help me? Thank you all.
As first correction I'd not recycle the XHR object from the outer loop in the inner loop.
When you say xmlhttp.onreadystatechange = function() ... in the inner loop, the xmlhttp is already in the readystate, obtained in the outer loop.
So, without further checking what is going on, I'd use two XHR objects (maybe like outerXmlhttp and innerXmlhttp). I'd also recreate the inner XHR for every cycle with:
var innerXmlhttp;
at the top of the outer closure.
Then, inside the cycle do:
innerXmlhttp = new XMLHttpRequest();
This is because of variable hoisting. If you just do this:
var innerXmlhttp = new XMLHttpRequest();
inside the cycle you may get a different behaviour. Just don't do it and write what you mean (hoist variables and assign them where you actually need it).
If all of this isn't enough ask a new, more precise question about what is going on.
This is your code with the corrections:
var outerXmlhttp = new XMLHttpRequest();
var url = "https://wjko5k6250.execute-api.us-east-2.amazonaws.com/motorcycle";
outerXmlhttp.onreadystatechange = function() {
var innerXmlhttp;
if (this.readyState == 4 && this.status == 200) {
var allmot = JSON.parse(this.responseText);
console.log(allmot);
for(var i = 0, len = allmot.Items.length; i < len; i++)
{
id=allmot.Items[i].id
var url1 = "https://wjko5k6250.execute-api.us-east-2.amazonaws.com/motorcycle/"+id;
console.log(url1);
innerXmlhttp = new XMLHttpRequest();
innerXmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myArr = JSON.parse(this.responseText);
console.log(myArr);
document.getElementById("img").src = myArr.Item.image;
document.getElementById("brd").innerHTML = myArr.Item.brand;
}
};
innerXmlhttp.open("GET", url1, true);
innerXmlhttp.send();
}
}
};
outerXmlhttp.open("GET", url, true);
outerXmlhttp.send();
EDIT: #Teemu's eagle eye
As #Teemu points out in his comment, if you reassign values over and over to the same DOM objects like this:
document.getElementById("img").src = myArr.Item.image;
document.getElementById("brd").innerHTML = myArr.Item.brand;
you're clearly overwriting whatever value was there before. Instead, you should create and append those DOM objects, more like this:
var img = document.createElement("img");
img.src = myArr.Item.image;
var brd = document.createElement("p");
brd.innerText = myArr.Item.brand;
document.getElementById("motlist").append(img);
document.getElementById("motlist").append(brd);
Obviously, you'll need a <div id="motlist"></div> element or some other parent in the DOM to which to append the new elements.
For paging you may also want to clear those elements in the list... but here we're going overboard.

How can I make AJAX work for me?

I make APIs all the time and I'm working on one called Swerer. Swerer is an easy and efficient way to use AJAX. Now the problem is when I use Swerer.getFile("file.txt") it returns undefined instead of the content. Any help would be appreciated.
/*
Complex.js 1.0.0
Dec 14, 2017
*/
(function(){
if(!document){
throw new Error("Complex.js needs a window with a document");
}
})();
var toString = Object.prototype.toString;
// Make X
X = function(){
};
X.extend = function(){
var target = arguments[0], obj, arg = arguments;
for (var i = 0; i < arg.length; i++) {
if(toString.call(arg[i]) == "[object Boolean]"){
if(arg[i] !== false){
if(!target){
obj = [];
}else{
for(i in target){
target[i] = obj[i];
}
}
}else{
obj = [];
}
}
}
return obj;
};
// Make constructors
X.extend({
// We are going to make something called Swerer
Swerer: function(){
X.call(this);
},
isFunction: function(obj){
if(toString.call(obj) == "[object Function]"){
return true;
}
},
});
var Swerer = X.Swerer;
Swerer = {};
// Note:
// When we are refering to Swerer in a Swerer function we can use the keyword 'this'
/*
Swerer.get("file.type", function(){
func(arg);
});
*/
// Xhr (XML Http Request) is built into Swerer
(XMLHttpRequest) ? Swerer.xhr = new XMLHttpRequest() : Swerer.xhr = new ActiveXObject("Microsoft.XMLHTTP");
Swerer.getFile = function(file){
var xhttp = this.xhr, content;
if(this.readyState == 4 && this.status == 200){
content = this.responseText;
}
xhttp.open("GET", file, true);
xhttp.send();
return content;
};
If you see any problems post a jsfiddle and I'll try to fix it. Thank you!
I must admit that I only focus on the XHR part of the code, but that should look something like this:
// Xhr (XML Http Request) is built into Swerer
(XMLHttpRequest) ? Swerer.xhr = new XMLHttpRequest() : Swerer.xhr = new ActiveXObject("Microsoft.XMLHTTP");
Swerer.getFile = function(file){
var xhttp = this.xhr;
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
return xhttp.responseText;
}
};
xhttp.open("GET", file, true);
xhttp.send();
};

Storing the value of variable in JS

Since my main language is C, I am used to pointers and I love them. Now I have some project which I need to finish in Javascript and I've got a problem which I don't know how to solve.
I want to store the value of a variable which I got from GET request. I have a script to send GET to PHP page, which then sends GET to my daemon written in C. When I get the string I wanted, I use length to measure the size of the string I got and in next GET request I want to send that number of bytes I got as the URL parameter.
window.onload = function() {
if (bytes === undefined) {
var bytes = 0;
}
var url = "/test/log.php?q=" + bytes;
function httpGet(url) {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", url, true);
xhttp.onload = function(e) {
if (xhttp.readyState === 4) {
if (xhttp.status === 200) {
console.log(xhttp.responseText);
var option = "";
obj = JSON.parse(xhttp.responseText);
for (var key in obj) {
option += obj[key];
}
document.getElementById("list").innerHTML = asdf;
bytes = option.length;
}
};
xhttp.onerror = function(e) {
console.error(xhttp.statusText);
}
};
xhttp.send();
}
var updateInterval = 2000;
function update() {
httpGet(url);
setTimeout(update, updateInterval);
}
update();
}
So, the focus is on the variable bytes. It should have the value 0 when the script is a first time called, and after every loop (it loops every 2 seconds, I didn't show the loop in the code) it should have the value of the previous length of received string.
You just need to make sure to add the bytes param onto your url in a way that changes with each call rather than just once at page load when it will always be 0.
window.onload = function() {
if (bytes === undefined) {
var bytes = 0;
}
var url = "/test/log.php?q=";
function httpGet(url) {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", url, true);
xhttp.onload = function(e) {
if (xhttp.readyState === 4) {
if (xhttp.status === 200) {
console.log(xhttp.responseText);
var option = "";
obj = JSON.parse(xhttp.responseText);
for (var key in obj) {
option += obj[key];
}
document.getElementById("list").innerHTML = asdf;
bytes = option.length;
}
};
xhttp.onerror = function(e) {
console.error(xhttp.statusText);
}
};
xhttp.send();
}
var updateInterval = 2000;
function update() {
httpGet(url + bytes);
setTimeout(update, updateInterval);
}
update();
}
Instead of a fixed value of url make it to a function and it will give you always the current Url with the modified version of bytes, if you modify it
You have only to change this parts
var url = ...
// to
function getUrl() {
return "/test/log.php?q=" + bytes;
}
...
// and
xhttp.open("GET", url, true);
// to
xhttp.open("GET", getUrl(), true);
I'd declare the variable in a context that doesn't empty its value when the function is called. So, you can declare your variable "bytes" before the function, and then looping through that function. In this case, the variable will hold the last value until you overwrite it.
That should work!

Parsing a web page using Javascript (ajax and jquery involved)

Trying to parse an ajax requested page for the word "function" and store the last matched character in an array. The only errors JSLint is returning are
unexpected ('space')
and
Combine this statement with the previous 'var' statement,
neither of which I believe should effect whether or not the code is executed. Any help is appreciated.
/*jslint browser: true*/
/*global $, jQuery, alert*/
$(document).ready(function () {
"use strict";
//retrieve page
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://abs.twimg.com/c/swift/en/init.27d18bf5a2b7d3e5fbcdbb86f85e7a534b11f06b.js", true);
xhr.responseType = "text";
xhr.send();
xhr.onload = function () {
//set variables to be compared
var page = xhr.responseText;
var word = "function";
//page and word locations
var i = 0;
var n = 0;
var page_loc = page[i];
var word_loc = word[n];
//matched result storage
var chain = [""];
// compare
while (n < word.length - 1) {
if (page_loc === word_loc) {
n = n + 1;
i = i + 1;
console.log(i);
} else {
i = i + 1;
}
}
//place matched result
chain.push(page_loc);
console.log(chain);
};
});
So regarding your comment and question.
You don't need to iterate over that string, if you only want to check whether a given string is present in a response, you can use a built-in function indexOf() or in newer browsers includes().
So the code would look as follow:
$(document).ready(function () {
"use strict";
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://abs.twimg.com/c/swift/en/init.27d18bf5a2b7d3e5fbcdbb86f85e7a534b11f06b.js", true);
xhr.responseType = "text";
xhr.onload = function () {
var page = xhr.responseText;
var word = "function";
if (page.indexOf(word) !== -1)
console.log([word[word.length-1]]);
};
xhr.send();
});

Returning an array and storing into another array, creating a multidimensional array

EDIT: I've tried to implemented the solution, but it still doesn't work. Sorry i'm a programming idiot. Thanks for the link to the page.
In my first function databaseBOscreens i am attempting to obtain symbols of stocks according to the filter selected for a broker and/or an asset class. The function sends a request to another file which performs SELECT statement to lookup for the relevant symbols using mysqli. The result would be an array of symbols, which i then encode and subsequently parsed in this js function.
Each symbol will then go through a second process, which is my second function generatePrice() to obtain the historical prices for that particular symbol. What i'm trying to do is to loop through all symbols to generate and store the priceArr as an array in the resultArr. Sorry for being unclear.
function databaseBOscreens(broker,assetClass) {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
var data = "broker=" + broker + "&category=" + assetClass;
xhr.open("POST", "generateSymbol.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(data);
xhr.onreadystatechange = receive_data;
function receive_data() {
if (xhr.readyState == 4 && xhr.status == 200)
{
var resultArr = JSON.parse(xhr.responseText);
var priceArr = new Array();
for(var ctr=0;ctr<resultArr.length;ctr++)
{
generatePrice(function(result,resultArr[ctr].symbol)){
priceArr[ctr] = result;
}
}
}
}
}
function generatePrice(callback,symbol) {
var xhr2;
if (window.XMLHttpRequest) {
xhr2 = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
xhr2 = new ActiveXObject("Microsoft.XMLHTTP");
}
var data2 = "symbol=" + symbol;
xhr2.open("POST", "generatePrice.php", true);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send(data2);
xhr2.onreadystatechange = receive_data2;
function receive_data2() {
if (xhr2.readyState === 4)
{
if (xhr2.status === 200)
{
var priceArr = JSON.parse(xhr2.responseText);
callback(priceArr);
}
}
}
}
function receive_data() {
if (xhr.readyState == 4 && xhr.status == 200)
{
var resultArr = JSON.parse(xhr.responseText);
var priceArray = new Array();
for(var ctr=0;ctr<resultArr.length;ctr++)
{
priceArray = generatePrice(resultArr[ctr].symbol);
}
alert(priceArray.length);
}
}
My function generatePrice() returns an array, but i can't seem to store it in another array to create a multidimensional array. I've search everywhere i can't seem to get it work. Thanks in advance for any help rendered
generatePrice function:
function generatePrice(symbol) {
var xhr2;
if (window.XMLHttpRequest) {
xhr2 = new XMLHttpRequest();
} else if (window.ActiveXObject) {
xhr2 = new ActiveXObject("Microsoft.XMLHTTP");
}
var data2 = "symbol=" + symbol;
xhr2.open("POST", "generatePrice.php", true);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send(data2);
xhr2.onreadystatechange = receive_data2;
function receive_data2() {
if (xhr2.readyState == 4 && xhr2.status == 200)
{
var priceArr = JSON.parse(xhr2.responseText);
return priceArr;
}
}
}
My function generatePrice() returns an array,
No it doesn't, it returns undefined. XHR is asynchronous, see How do I return the response from an asynchronous call?.
priceArray = generatePrice(resultArr[ctr].symbol);
I can't seem to store it in another array
You didn't attempt to do so, you just stored the result of every call in the priceArray variable - instead of in a property of the array. Use
priceArray[ctr] = generatePrice(resultArr[ctr].symbol);
// or
priceArray.push( generatePrice(resultArr[ctr].symbol) );

Categories

Resources