I was trying to display an ajax request from a web service in my HTML.
I can display the result in the console but can not retrieve the result and then display it in my HTML.
I want to display the result of the request in "weather-result" div by clicking the "ask-weather" button.
Her is my code.
Thank everyone.
const askWeather = function(result){
var request = new XMLHttpRequest();
request.onreadystatechange = function subFunction() {
if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
result = JSON.parse(this.responseText);
return result.current_condition.condition;
}
};
request.open("GET", "https://www.prevision-meteo.ch/services/json/paris");
request.send();
}
const ask = document.getElementById('ask-weather');
ask.addEventListener('click', function(){
const weatherResult = document.getElementById('weather-result');
weatherResult.innerHTML = askWeather();
});
<html>
<head>
<link rel="stylesheet" type="text/css" href="base.css">
</head>
<body>
<div><button id="ask-weather">Quelle est la météo sur Paris ?</button></div>
<div id="weather-result"></div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
This seems like a asynchronous/callback problem here. When the button is clicked, it sends a request somewhere and will return right away (with undefined in case of the code above - this can be checked by saving it in a variable and console.log it).
When askWeather() is called, it could return something itself. The return in request.onreadystatechange cannot return for askWeather as it's happening multiple times and later - after askWeather is done and the request is being sent.
If you pass a function a variable and set it to something new in its body, it will not be changed for the caller. That means doing result = ... does not really help, if you wanted to pass a variabel and get it set by the inner function.
A different approach is necessary to handle this. Here are a few alternatives:
To keep it as most similar to the code you had, you can set the innerHTML in the onreadystatechange function:
const askWeather = function() {
var request = new XMLHttpRequest();
request.onreadystatechange = function subFunction() {
if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
const result = JSON.parse(this.responseText);
// set it here directly
const weatherResult = document.getElementById('weather-result');
weatherResult.innerHTML = result.current_condition.condition;
}
};
request.open("GET", "https://www.prevision-meteo.ch/services/json/paris");
request.send();
}
const ask = document.getElementById('ask-weather');
ask.addEventListener('click', function() {
askWeather();
});
Make it more general and let askWeather use a callback (call a function when it's "done"):
const askWeather = function(callback) {
var request = new XMLHttpRequest();
request.onreadystatechange = function subFunction() {
if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
const result = JSON.parse(this.responseText);
// send the result to the passed "callback" function
callback(result.current_condition.condition);
}
};
request.open("GET", "https://www.prevision-meteo.ch/services/json/paris");
request.send();
}
const ask = document.getElementById('ask-weather');
ask.addEventListener('click', function() {
askWeather(function (result) { // this whole function is the "callback" parameter
const weatherResult = document.getElementById('weather-result');
weatherResult.innerHTML = result;
});
});
(a) Let askWeather return a promise and use it in the caller
const askWeather = () => new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.onreadystatechange = function subFunction() {
if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
const result = JSON.parse(this.responseText);
// send the result to the passed "callback" function
resolve(result.current_condition.condition);
}
// not sure about the error path here, but something like this:
if (this.readyState == XMLHttpRequest.DONE && this.status != 200) {
reject(new Error("There was an error with the XMLHttpRequest!"));
}
};
request.open("GET", "https://www.prevision-meteo.ch/services/json/paris");
request.send();
});
const ask = document.getElementById('ask-weather');
ask.addEventListener('click', function() {
askWeather()
.catch((err) => weatherResult.innerHTML = err.message) // to handle possible errors, maybe?
.then((result) => { // like the callback solution, but as promise!
const weatherResult = document.getElementById('weather-result');
weatherResult.innerHTML = result;
});
});
});
(b) Additionally to the Promise solution, in newer browsers there is already async and await syntax:
ask.addEventListener('click', async function() {
try {
const result = await askWeather(); // this "pauses" until the Promise return of `askWeather` resolves (or throws an error if it doesn't)
const weatherResult = document.getElementById('weather-result');
weatherResult.innerHTML = result;
} catch (e) {
// error could be handled here
}
});
Instead of XMLHttpRequest, use the fetch API, which usually should be available if Promises are available in the browsers you support. The solution is in the comments of the original question. With most modern browsers, this should work:
ask.addEventListener('click', async () => {
const response = await fetch("https://www.prevision-meteo.ch/services/json/paris");
const result = await response.json();
const weatherResult = document.getElementById('ask-weather');
weatherResult.innerHTML = result.current_condition.condition;
});
If you don't have to support IE, I would use the fetch alternative.
I hope the other alternatives make it clear, asynchronous patterns can be resolved in JavaScript.
function getJSON(path) {
return new Promise(function(resolve, reject) {
var xhttp = new XMLHttpRequest();
xhttp.open('GET', path, true);
xhttp.onreadystatechange = function() {
if (this.readyState === 4) {
if ((this.status >= 200 && this.status < 300) || this.status === 304) {
var response = JSON.parse(this.responseText);
resolve(response);
} else {
var error = this.statusText;
reject('Http/App Error: ' + error);
}
}
}
xhttp.onerror = processError;
xhttp.onabort = processError;
xhttp.send();
xhttp = null;
function processError(err) {
reject('Network Error: ' + err.target.status);
}
});
}
const ask = document.getElementById('ask-weather')
const weather = document.getElementById('weather-result')
const endpoint = 'https://www.prevision-meteo.ch/services/json/paris'
ask.addEventListener('click', function() {
getJSON(endpoint).then((success) => {
const response = success.current_condition.condition
weather.innerHTML = response
}, (error) => {
console.log(error)
})
})
This is a simple example using Promise. See the fiddle working.
Related
Hei Friends I just start to learn XMLHttpRequest
I write this code its work on console.log but when I append it to HTML it shows me Object Object i know its object but I wanna show the data inside the object in HTML HELP PLEASE
return new Promise((resolve, reject) => {
let myRequest = new XMLHttpRequest();
myRequest.onload = function () {
if (this.readyState === 4 && this.status === 200) {
resolve(JSON.parse(myRequest.responseText));
} else {
reject(Error(this.statusText));
}
};
myRequest.open("GET", apiURL, true);
// send request to API
myRequest.send();
});
};
apiGenerator("https://random-data-api.com/api/stripe/random_stripe").then(
(resulit) => {
console.log(resulit);
let newElement = document.createElement("div");
let elementText = document.createTextNode(resulit);
newElement.appendChild(elementText);
document.body.appendChild(...newElement);
},
(error) => console.log(error)
);
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>
I am very new to JS, trying to create simple page which does next:
takes IP of some server
then sends a get request to this server
parses get response,
adds filtered lines to the table on html page.
I was able to do all the steps through the browser console but when, moving to the JS file with get function for some reason function does not return value.
In below code snip line 6 will print undefined in the console.
Any idea how to return "statuses" from the function getStatus?
Should it be some timeout between line 5 and 6?
Thanks!
$("input[type='text']").keypress(function(event){
if(event.which === 13){
var address = $(this).val();
var urlStat = 'http://'+address+':666/bla?open=stats';
var status = getStatus(urlStat);
console.log(status);
$("input[type='text']").val('');
$('table').append("<tr><th>"+address+"</th><th><ul></ul></th><th></th></tr>");
}
});
function getStatus(url){
var xhr = new XMLHttpRequest;
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var regexStatus = /(\w+ state:.*?)</g
var response = xhr.responseText;
var statuses = response.match(regexStatus);
console.log('Inside function getStatus'+statuses);
return statuses;
};
}
};
The problem with your code is that the status is returned after your your request has been sent. That gives a small delay. Because you immediatly ask for the return value of getStatus, you will get undefined.
You could solve this problem with a callback function:
function getStatus(url,callback){
var xhr = new XMLHttpRequest;
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var regexStatus = /(\w+ state:.*?)</g
var response = xhr.responseText;
var statuses = response.match(regexStatus);
console.log('Inside function getStatus'+statuses);
if(callback) callback(statuses);
};
}
};
You call the getStatus function with a function, which is called after you got a response from you request.
E.g:
getStatus(url,function(statuses){
console.log(statuses);
});
EDIT
For a better and longer explanation, consider to check out How do I return the response from an asynchronous call?
I have a redux-form that is passing props to my action. The property this.props.userImages[0] is an image file from a file input on that form. I'm then taking that image and making and XMLHttpRequest to Cloudinary which generates a url for that image. Once I receive the url data (xhr.responseText), I'd like to then merge it with my other props to that I can then post all of my props to an API (all form info + newly created image URL).
I know that I have to wait for my request to generate a url to resolve, but having issues with getting it right before I can pass it onto my other function which can take that info and merge it in with props before posting to my API.
//..
function generateUrl(props) {
// Grabs image file from my form's file input and uploads
// to cloudinary service so that a URL can be generated
const cloudinaryURL = 'https://api.cloudinary.com/v1_1/<my_name>/image/upload';
const apiKey = 'secret_key';
const uploadPreset = 'test_preset';
const data = new FormData();
data.append('file', props.userImages[0]);
data.append('upload_preset', uploadPreset);
data.append('api_key', apiKey);
const xhr = new XMLHttpRequest();
xhr.open('POST', cloudinaryURL, true);
xhr.send(data);
xhr.onReadyStateChange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
return JSON.parse(xhr.responseText);
}
};
return xhr.onReadyStateChange();
}
export function createReview(props) {
const imageUrl = generateUrl(props);
const mergedProps = //...
// Here I'd like to merge my newly generated
// url back into props before I post to my API like so...
const request = axios.post(`${REQUEST_URL}/api`, mergedProps)
return {
type: CREATE_REVIEW,
payload: request
}
};
Any and all help is greatly appreciated.
This has nothing to do with promises in the context of your example XMLHttpRequest based code.
The assumption your making is that the callback assigned to onReadyStateChange does something with it's return value. Instead anything returned from that function is dutifully ignored.
What you want is to pass the value onward through another callback.
function generateUrl(props, callback) {
// Do things here
xhr.onReadyStateChange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(JSON.parse(xhr.responseText));
}
};
}
generateUrl(props, (response) => {
const mergedProps = // Use response as expected.
});
Since you mentioned promise and your using ES2015 we can convert this to actually use promises which is probably what you wanted to begin with.
function generateUrl(props) {
return new Promise((resolve, reject) => {
const cloudinaryURL = 'https://api.cloudinary.com/v1_1/<my_name>/image/upload';
const apiKey = 'secret_key';
const uploadPreset = 'test_preset';
const data = new FormData();
data.append('file', props.userImages[0]);
data.append('upload_preset', uploadPreset);
data.append('api_key', apiKey);
const xhr = new XMLHttpRequest();
xhr.onReadyStateChange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
resolve(xhr.responseText);
} else {
reject(new Error(`Failed HTTP request (${xhr.status})`));
}
};
xhr.onerror = reject;
xhr.open('POST', cloudinaryURL, true);
xhr.send(data);
});
}
generateUrl(props)
.then(JSON.parse)
.then(results => {
// Do something with response
})
.catch(error => {
// Do something with the error
});
I'm trying to get a webworker to poll a web server interface on the same machine every second or so. Most articles I have read say to avoid setInterval and use setTimeout instead but I have yet to find an example that uses AJAX instead of Jquery.
The code I have so far is below:
(function poll() {
setTimeout(function() {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.status === 200) {
responseObject = JSON.parse(xhr.responseText);
var newContent = '';
newContent += responseObject.cmd;
console.log(newContent);
}
}
xhr.open('GET', 'http://localhost:8194/screen_update/1000', true);
xhr.send(null);
setTimeout(poll, 1000);
}, 1000);
})();
The preferred output would be to poll the server each second which should in theory be more than adequate for the response to come through. I only want one request on the go at a time so if I end up with a request taking more than a second it just dumps the request (rather than queuing it) and issues a new request.
The above code polls okay but doesn't complete for 2 seconds so I've obviously got my setTimeout mixed up somewhere. Where do I correct this code?
I did just that a few days ago.. and while it may not be the most elegant, it works fine so far.
I have the worker handle the timeout / check interval, not the main JS. So I guess that's one more thing that the UI doesn't need to handle. Here is my worker code:
function checkStatus() {
console.log("statusCheck started");
var ajaxRequest;
try { ajaxRequest = new XMLHttpRequest(); // Opera 8.0+, Firefox, Safari
} catch (e) { try { // Internet Explorer Browsers
ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) { try {
ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) { // Something went wrong
console.error("AJAX not possible");
return false;
}
}
}
// Create a function that will receive data sent from the server
ajaxRequest.onreadystatechange = function() {
if(ajaxRequest.readyState == 4) {
self.postMessage(ajaxRequest.responseText);
var timer;
timer = self.setTimeout(function(){
checkStatus();
}, 1000);
}
}
ajaxRequest.open("GET", "/worker_statusCheck.php", true);
ajaxRequest.send(null);
}
this.onmessage = function(e){
checkStatus(); // the message comes in just once on pageLoad
};
Define a variable that determines if ajax finished or not. If function is called while ajax hasn't finished yet, you can exit the function and wait for the next call.
var stillWorking = false;
(function poll() {
if(stillWorking) return false;
stillWorking = true;
setTimeout(function() {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState == 4) stillWorking = false;
if (xhr.status === 200) {
responseObject = JSON.parse(xhr.responseText);
var newContent = '';
newContent += responseObject.cmd;
console.log(newContent);
}
}
xhr.open('GET', 'http://localhost:8194/screen_update/1000', true);
xhr.send(null);
setTimeout(poll, 1000);
}, 1000);
})();
You can call same function when you get response of AJAX. In this way no need to check that currently AJAX is in process or not.
function poll() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange= function() {
if (xhr.status === 200) {
responseObject = JSON.parse(xhr.responseText);
var newContent = '';
newContent += responseObject.cmd;
console.log(newContent);
}
if (xhr.readyState == 4)
{
setTimeout(function(){ poll();},1000);
}
}
xhr.open('GET', 'http://localhost:8194/screen_update/1000', true);
xhr.send(null);
};
setTimeout(function(){ poll();},1000);
If you want to use onload callback then callback code should be
xhr.onload= function() {
if (xhr.status === 200) {
responseObject = JSON.parse(xhr.responseText);
var newContent = '';
newContent += responseObject.cmd;
console.log(newContent);
}
setTimeout(function(){ poll();},1000);
}
Because you are using HTML5 WebWorker, probably, you can use window.fetch which uses promises (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), I think that browser support is almost the same.
Here is an example:
((url, INTERVAL, configs) => {
const MAX_ERRORS = 4;
let errors = 0;
var poll = () => window.setTimeout(getData, INTERVAL);
function getData() {
return window
.fetch(url, configs)
.then(res => res.json())
.then(data => {
errors = 0;
poll();
return data;
})
.then((data) => {
console.log("new data available", data);
})
.catch(() => {
if(errors >= MAX_ERRORS) {
console.log("GIVING UP");
return;
}
errors += 1;
return poll();
})
;
}
return poll();
})("http://jsonplaceholder.typicode.com/posts/1", 1000, {
method: 'GET'
});