Background
I am making a request every 5 seconds using XMLHttpRequest and I want to print my name when I receive the response.
To do this I am using onreadystatechange which allows me to define a callback function when I receive the answer.
Problem
To achieve this, I am using a class. When I first initiate the class I say my name immediately, and then I start a process using setTimeInterval to make a request every 5 seconds and see my name.
The problem is that I see my name the first time, but then I never see it again. The issue is that this seems to be in different context, and thus this.sayMyName() doesn't exist because it doesn't belong to the xhr object.
What I tried
To fix this I tried using scoping by following another StackOverflow question but unfortunately this remains undefined.
Code
class Cook {
constructor() {
// innitial call so we don't have to wait
//5 seconds until we see my name
this.getCookInfo();
setInterval(this.getCookInfo, 5000);
}
getCookInfo() {
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://best.cooks.in.the.world.org/";
xhr.open(method, url, true);
//Call a function when the state changes.
xhr.onreadystatechange = (self => {
return () => {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200)
self.sayMyName();
};
})(this);
}
sayMyName() {
console.log("Heisenberg");
}
}
Questions:
Is there a way to fix this code without have to pass a context object to the setInterval function?
Note
Kudos++ for those of you who get my reference :P
bind the this.getCookInfo function to this
then you can rally simplify your code
class Cook {
constructor() {
// innitial call so we don't have to wait
//5 seconds until we see my name
this.getCookInfo();
setInterval(this.getCookInfo.bind(this), 5000);
}
getCookInfo() {
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://best.cooks.in.the.world.org/";
xhr.open(method, url, true);
//Call a function when the state changes.
// no need for self gymnastics any more
// using onload, no need to test reasyState either
xhr.onload = e => {
if (xhr.status == 200)
this.sayMyName();
};
// your code lacks one thing
xhr.send();
}
sayMyName() {
console.log("Heisenberg");
}
}
An alternative -
class Cook {
constructor() {
this.getCookInfo();
}
getCookInfo() {
var getit = () => {
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://best.cooks.in.the.world.org/";
xhr.open(method, url, true);
//Call a function when the state changes.
xhr.onload = e => {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200)
this.sayMyName();
};
xhr.send();
};
getit();
setInterval(getit, 5000);
}
sayMyName() {
console.log("Heisenberg");
}
}
I'm only 99% sure this is right though :p
Related
Imagine this function:
function myMainFunction() {
doSomeInitialStuff();
// more stuff..
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
// Now that we know we received the result, we can do the heavy lifting here
if (xhr.status == 200) {
console.log("ready 200");
let result = JSON.parse(xhr.responseText);
doStuff(result);
// and much more stuff..
} else {
console.log("error", xhr.status);
return undefined;
}
}
};
xhr.open("GET", "http://example.com", true);
xhr.send(null);
}
This works fine, but it is impossible to test, and this function has become a monster.
So I'd like to refactor it, by separating all the different parts in their own unique functions.
The problem is, I do not know how to extract the XHR part and still keep it working.
I cannot use Promises nor asnyc/await and have to stick to using plain XHR.
What I'd normally do is to create a seperate async function for the ajax call (or the xhr in this case). Simply await it's result and go from there. Easy to separate. But I do not have the luxury of await or anything this time.
What I am trying to get at is something like this
function refactoredMyMainFunction() {
doSomeInitialStuff();
// more stuff..
let result = xhrFunction();
doStuff(result); // result would be undefined here, since I cannot wait for the xhr request to finish.
}
You can implement a callback-based API:
function myMainFunction() {
doSomeInitialStuff();
// more stuff..
xhrFunction(doStuff);
}
function xhrFunction(cb) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
// Now that we know we received the result, we can do the heavy lifting here
if (xhr.status == 200) {
console.log("ready 200");
let result = JSON.parse(xhr.responseText);
cb(result);
// and much more stuff..
} else {
console.log("error", xhr.status);
return undefined;
}
}
};
xhr.open("GET", "http://example.com", true);
xhr.send(null);
}
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.
New to js. I am developing a personal site which uses tribute.js to have an #mention feature. In my case, I need to retrieve the mention list from a remote server. The official document gives an example to implement it. The thing confusing me is the meaning of cb parameter which is not even defined in anywhere. Could anyone help to explain it?
{
//..other config options
// function retrieving an array of objects
values: function (text, cb) {
remoteSearch(text, users => cb(users));
},
lookup: 'name',
fillAttr: 'name'
}
// ajax
function remoteSearch(text, cb) {
var URL = "YOUR DATA ENDPOINT";
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
cb(data);
} else if (xhr.status === 403) {
cb([]);
}
}
};
xhr.open("GET", URL + "?q=" + text, true);
xhr.send();
}
Meaning is hiding in the title of your question, cb means callback :)
It gets called by the remoteSearch function which in its turn was called with cb parameter passed by the tribute.js engine.
I have this code to make an ajax request, but according to Chrome Inspector the callback associated with the request is being called twice (by this I mean the response is being logged into the console twice), 2 more logs are being printed without any content. Here's the code:
var ajax = {
pull: function (settings) {
settings.type = 'get';
settings.callback = typeof (settings.callback) === 'function' ? settings.callback : false;
settings.data = settings.data ? settings.data : null;
return this.request(settings.url, settings.type, settings.callback, settings.data);
},
request: function (url, type, callback, data) {
var ids = ['MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'],
xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
for (var i = 0; i < ids.length; i++) {
try {
xhr = new ActiveXObject(ids[i]);
break;
} catch (e) {}
}
}
if (callback) {
xhr.onreadystatechange = function () {
callback(xhr);
};
}
xhr.open(type, url, true);
if (type.toUpperCase() === 'GET') {
xhr.send();
} else if (type.toUpperCase() === 'POST') {
xhr.send(data);
}
}
}
ajax.pull({
url: 'http://localhost/my/twtools/scripts/ajax.php',
callback: function (xhr) {
console.log(xhr.response);
}
});
xhr.onreadystatechange has several steps (numbered from 0 top 4 I do believe something like 0 = uninitialized, 1 = starting etc, although I can't rember the exact names of the steps anymore, a quick google should find them), and each step is calling your callback. If I remember correctly, the last stage is 4, so I do believe you need to check something like this
if (xhr.readyState == 4 && xhr.status == 200)
{
// call has finished successfully
}
inside you callback, i.e. to check that it is all finished and got a successful response
I've been spoilt by jQuery these days (so much easier to do with jQuery), been quite a while since I wrote raw ajax
You're using onreadystatechange, which gets called more than once (once each state change).
Try using
xhr.onload = function() {
callback(xhr);
};
I'm new to Ajax and this has me stumped. It's relatively simple but trips up on the onclick handler with the first xhr.open(). Firebug says it's not a function, my guess is the XMLHttpRequest object isn't being created, but I'm not sure. Anyone have any ideas?
Thanks in advance.
function init(){
function getXMLHTTP(){
var xhr = false;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
return xhr;
}
function updatePage(theData){
$('results').innerHTML = theData; //TODO: pretty this up with mootools
}
var xhr = getXMLHTTP();
if (xhr) {
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var theData = xhr.responseText;
updatePage(theData);
}
else{
alert("Error communicating to web service!");
}
}
}
$('submit_btn').onclick = function(xhr){
if ($('state').value != "") {
var theValue = $('state').value;
xhr.open("GET", "/inc/calc.php?state="+theValue, true); //Ajax 101: 3rd argument marks object as asynchronous
xhr.send(NULL);
}
else if ($('city').value != "") {
xhr.open("GET", "/inc/calc.php?city="+$('city').value, true);
xhr.send(NULL);
}
else if ($('county').value != "") {
xhr.open("GET", "/inc/calc.php?county="+$('county').value, true);
xhr.send(NULL);
}
else {
//the form is empty, or just down payment is filled. returns whole freakin' table.
xhr.open("GET", "/inc/calc.php", true);
xhr.send(NULL);
}
}
}
}
The problem with your code is the onclick function. You have put the xhr in the argument list to the function. Remember that when a function is called, the value of this variable is set by the caller. In this case it would be the event dispatcher, and it would probably set the xhr variable to an event object, which does not have an open function.
If you remove the xhr variable from the argument list of the onclick function, then it will look for the xhr variable in the parent scope, the global scope, and it will find it there, and it should work. I haven't tested it though.
I'm assuming you are using some kind of framework as well (judging by the frequent use of $, and the reference to mootools). This framework probably has an ajax function built in, as well as a cross browser event model. Try using it instead, you will run into a lot less problems.
When assigning the onclick handler you create a new function that takes a parameter called xhr:
$('submit_btn').onclick = function(xhr){
...
xhr.open("GET", "/inc/calc.php?state="+theValue, true);
...
}
The click on the button won't pass a XMLHttpRequest object the the handler, so xhr will not have an open() method. The global definition of xhr doesn't matter because it's shadowed by the local parameter definition.
Generally you should just generate a new local XMLHttpRequest object when you need it, not try to use a global one. For example use an onclick function that creates a new local XMLHttpRequest:
$('submit_btn').onclick = function(){
var xhr = getXMLHTTP();
xhr.open("GET", "/inc/calc.php?state="+theValue, true);
...
}