I'm having problems with this piece of code; the return value comes back as 'undefined'. What's the problem?
var fx = null;
xmlhttp.open("GET", URL ,false);
xmlhttp.onreadystatechange=function()
{
alert("enter func");
if (xmlhttp.readyState==4)
{
if (xmlhttp.status == 200)
{
alert(fx);
fx = xmlhttp.responseText;
return fx;
}
else
{
alert("Error" + xmlhttp.statusText);
}
}
}
Newer code:
function getData(callback)
{
xmlhttp.open("GET", URL ,false);
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4)
{
if (xmlhttp.status == 200)
{
alert(xmlhttp.responseText);
cbfunc(xmlhttp.responseText);
}
else
{
alert("Error" + xmlhttp.statusText);
}
}
}
xmlhttp.send(null);
}
How I'm calling it:
getData( function cbfoo(txt)
{
//document.form.autodate.value=txt;
alert(txt);
alert(document.form.autodate.value);
});`
Update based on your edit
function getData(callback)
{
// you should move the creation of xmlhttp in here
// so you can make multiple getData calls if needed
// if you keep xmlhttp outside the function the different calls to getData will interfere
// with each other
xmlhttp.open("GET", URL ,false); // false should be true, to make it async
...
{
alert(xmlhttp.responseText);
cbfunc(xmlhttp.responseText); // your function gets passed as the
// parameter "callback" but you're
// using "cbfunc" here instead of "callback"
...
getData(function cbfoo(txt) // you can omit the function name here
...
Fixing those issues should make the code work.
Old answer
You're calling the XMLHttpRequest in Synchronous mode, that means that it will block the script until the request has finished, since you're assigning the onreadystatechange callback after the blocking call (that means after the request has already finished) your code never gets notified.
Since the synchronous mode blocks the script, it also blocks the Browser's UI, so it's not recommended to use this mode.
You should (for 99% of the cases) use the Asynchronous mode and use a callback to handle the data, since xmlhttp.open does not return the return value of the onreadystatechange callback, it simply returns undefined immediately when run in async mode.
Now a common pattern is to write a wrapper for the request and pass an anonymous function to this wrapper, which later will get called back when the request has finished.
function doRequest(url, callback) {
var xmlhttp = ....; // create a new request here
xmlhttp.open("GET", url, true); // for async
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
if (xmlhttp.status == 200) {
// pass the response to the callback function
callback(null, xmlhttp.responseText);
} else {
// pass the error to the callback function
callback(xmlhttp.statusText);
}
}
}
xmlhttp.send(null);
}
You can now do a request and supply a function that will get called as soon as the request finishes, inside of that function you then do whatever you want to do with the response.
doRequest('http://mysite.com/foo', function(err, response) { // pass an anonymous function
if (err) {
alert('Error: ' + err);
} else {
alert('Response: ' + response);
}
});
This is the common programming model in the browser, always go with a asynchronous solution, if you block the script you block the whole Browser.
Related
I am working on the example from this page, trying to learn some javascript: https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX/Getting_Started
And I have a little web server and a status page that I made that I want to use to check the status of the web server. The way I have it built is I want to check the status of the web server onLoad rather than with a button.
HTML
Just using: <body onload=pingSite();> to try to automatically run the check when the page loads.
Javascript
<script>
let xhr = new XMLHttpRequest(); <--- The question and the problem.
function pingSite() {
var xhr = new XMLHttpRequest();
response = 'xyz';
//If an XMLHTTP instance cannot be created
if(!xhr)
{
response = 'Internal error: Cannot create XMLHTTP instance.'
return response;
}
xhr.onreadystatechange = HandlePing;
xhr.open('GET', 'mysite.com');
xhr.send();
return response;
}
function HandlePing() {
//If the request has finished
if(xhr.readyState === XMLHttpRequest.DONE)
{
try {
//If the status is 200 (IE we have recieved a response back indicating the server is up.)
if(xhr.status === 200)
{
alert("Server is up");
}
else
{
alert("There was a problem with the request");
}
//If the server is down.
} catch (error) {
alert(`Caught Exception: ${error.description}`);
}
}
else
{
response = 'Pinging...';
}
}
</script>
The problem I have is two fold:
1.) The only way I can get this to work is by creating a global variable in my script above both of the functions in order to get the call to work. I have a gut feeling this is really dangerous and bad practice. Is it, and if so, what is a better way to approach this problem?
2.) The way I have it set up seems to work, but it doesn't return any indication that it worked at all. There is no alert. There is no response. The console is empty. Am I missing something? Do I need an event handler in the HTML despite the fact I am doing it onload?
You could dodge the global variable by creating an anonymous inline function that passes xhr as an argument:
xhr.onreadystatechange = () => HandlePing(xhr);
Or with bind:
// same as arrow function above but harder to read
xhr.onreadystatechange = HandlePing.bind(null, xhr);
Or, assuming you don't need it anywhere else, you could move the HandlePing function declaration into the pingSite function:
function pingSite() {
function handlePing() {
if(xhr.readyState === XMLHttpRequest.DONE)
// ...
}
const xhr = new XMLHttpRequest();
// ...other stuff...
const xhr.onreadystatechange = HandlePing;
// ...
}
A few additional thoughts:
You should use fetch instead of XMLHttpRequest.
You should use addEventListener instead of attaching an onload attribute to the body.
fetch uses Promises, which make it easier to manage asynchronous behavior.
The skeletal implementation might look something like this:
window.addEventListener('load', handleLoadEvent);
function handleLoadEvent(e) {
fetch('http://example.com')
.then( response => {
// do stuff with the response
})
.catch( error => {
// deal with errors
})
}
And if you didn't want to pollute the global namespace with the handleLoadEvent function, you could wrap this all in an IIFE:
(function () {
window.addEventListener('load', handleLoadEvent);
function handleLoadEvent(e) {
fetch('http://example.com')
.then( response => {
// do stuff with the response
})
.catch( error => {
// deal with errors
})
}
})()
Or if you prefer async/await you could write the handleLoadEvent function that way:
async function handleLoadEvent(e) {
try {
const response = await fetch('http://example.com');
// do stuff with response
}
catch (e) {
// deal with error
}
}
I am using Async XMLHttpRequest to make an API call. Here's the workflow of my program,
first_function(){
var valueToBeReturned = 0;
makeRequest(myCallback)//function for API call
/*rest of the code*/
console.log("print second");
return valueToBeReturned;
}
function makeRequest(callback){
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "my_url", true);
xhttp.send(null);
xhttp.onload = function() {
if(xhttp.readyState === 4) {
if(xhttp.status === 200) {
response = JSON.parse(xhttp.responseText);
callback(null, response);
}
}
}
}
function myCallback(data){
console.log("print first")
}
Now what happens is every time I run it, the entire code in the first function is executed and then the code in makeRequest is executed. I understand JS is synchronous in nature and everything. But I'm not able to get my work done here, which is fisrt it makes API call, then callback is executed, then the code after makeRequest. What am I doing wrong here?
PS this is not the actual code, just to demonstrate the flow of my program
You need to put callback as a parameter in makeRequest. I'm not sure what that null is there for, though. If you want "print second" to print second, you'll need to execute it after myCallback - maybe insert another callback?
function first_function(){
var valueToBeReturned = 0;
makeRequest(myCallback, restOfTheCode)
function restOfTheCode() {
/*rest of the code*/
console.log("print second");
}
}
function makeRequest(callback1, callback2){
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "my_url", true);
xhttp.send(null);
xhttp.onload = function() {
if(xhttp.readyState === 4 && xhttp.status === 200) {
const response = JSON.parse(xhttp.responseText);
callback1(response);
callback2(response);
}
}
}
function myCallback(data){
console.log("print first");
}
But this whole thing would be a whole lot nicer if you used Fetch and Promises instead:
function makeRequest() {
return fetch('my_url')
.then(response => response.JSON())
}
// consume the request:
makeRequest()
.then(responseJSON => {
// do stuff with the responseJSON
});
I have a function called urlExists that I used in several projects without problem. Now I'm in a patients panel and need to check if a js file exists before call a function addEventListener:
function urlExists(url){
var http = new XMLHttpRequest();
http.open('HEAD', url, false);
http.onreadystatechange = function(){
if(request.readyState==4){
return true;
}else{
return false;
}
}
}
//other part of code
if (urlExists('../../medvoice/speaker.js')){
beep.addEventListener('ended', function() {
var parameters = {
target: dados,
tts: {
name: 'lianetts'
},
binder: 'speak',
path: {
name: '../../medvoice/mediapool/',
link: '/medhosphsl/medvoice/mediapool/'
}
};
var speaker = new Speaker(parameters);
});
}
When a put console log inside urlExists, console shows the test phrase, but inside onreadystatechange don't.I'm missing something?
There are several problems:
Your return true and return false aren't returning from urlExists, they're returning from the onreadystatechange callback. urlExists has no explicit return anywhere, so calling it always results in undefined.
You never call send to fire the request.
You're using readyState to decide whether the resource exists, but that just tells you the state of the request; you should be checking status.
If you must use a synchronous request, you don't need or want an onreadystatechange handler at all. Here's a version with all those issues addressed:
function urlExists(url){
var http = new XMLHttpRequest();
http.open('HEAD', url, false);
http.send();
return http.status === 200;
}
For completeness: Synchronous ajax request make for poor UX and may well be phased out entirely at some point. Instead, use an asynchronous request and have urlExists return a promise:
function urlExists(url){
return new Promise(function(resolve) {
var http = new XMLHttpRequest();
http.open('HEAD', url);
http.onreadystatechange = function() {
if (http.readyState === 4) {
resolve(http.status === 200);
}
};
http.send();
});
}
// Usage:
urlExists("some-url").then(function(exists) {
console.log("Exists? " + exists);
});
Or the more modern way with fetch and ES2015+ arrow functions:
function urlExists(url) {
return fetch(url, {method: "HEAD"}).then(response => response.ok);
}
// Usage:
urlExists("some-url").then(exists => console.log("Exists? " + exists));
Or we could embrace ES2017 (out next month!):
async function urlExists(url) {
return (await fetch(url, {method: "HEAD"})).ok;
}
// Usage (in another async function):
console.log("Exists? " + await urlExists("some-url"));
Side note: urlExists will only work for URLs you can access via ajax, so typically only ones in the same origin. If that's your use case, great, but I thought I'd mention it.
I'm new to JavaScript programming. I'm now working on my Google Chrome Extension. This is the code that doesn't work... :P
I want getURLInfo function to return its JSON object, and want to put it into resp. Could someone please fix my code to get it work?
function getURLInfo(url)
{
var xhr = new XMLHttpRequest();
xhr.open
(
"GET",
"http://RESTfulAPI/info.json?url="
+ escape(url),
true
);
xhr.send();
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
{
return JSON.parse(xhr.responseText);
}
}
}
var resp = getURLInfo("http://example.com/") // resp always returns undefined...
Thanks in advance.
You are dealing with an asynchronous function call here. Results are handled when they arrive, not when the function finishes running.
That's what callback functions are for. They are invoked when a result is available.
function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
// defensive check
if (typeof callback === "function") {
// apply() sets the meaning of "this" in the callback
callback.apply(xhr);
}
}
};
xhr.send();
}
// ----------------------------------------------------------------------------
var param = "http://example.com/"; /* do NOT use escape() */
var finalUrl = "http://RESTfulAPI/info.json?url=" + encodeURIComponent(param);
// get() completes immediately...
get(finalUrl,
// ...however, this callback is invoked AFTER the response arrives
function () {
// "this" is the XHR object here!
var resp = JSON.parse(this.responseText);
// now do something with resp
alert(resp);
}
);
Notes:
escape() has been deprecated since forever. Don not use it, it does not work correctly. Use encodeURIComponent().
You could make the send() call synchronous, by setting the async parameter of open() to false. This would result in your UI freezing while the request runs, and you don't want that.
There are many libraries that have been designed to make Ajax requests easy and versatile. I suggest using one of them.
You can't do it at all for asynchronous XHR calls. You cannot make JavaScript "wait" for the HTTP response from the server; all you can do is hand the runtime system a function to call (your handler), and it will call it. However, that call will come a long time after the code that set up the XHR has finished.
All is not lost, however, as that handler function can do anything. Whatever it is that you wanted to do with a return value you can do inside the handler (or from other functions called from inside the handler).
Thus in your example, you'd change things like this:
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
{
var resp = JSON.parse(xhr.responseText);
//
// ... whatever you need to do with "resp" ...
//
}
}
}
For small edit talking about post: https://stackoverflow.com/a/5362513/4766489
...
if (typeof callback == "function") {
//var resp = xhr.responseText;
var resp = JSON.parse(xhr.responseText);
callback(resp);
}
...
And when you call
...
function(data) {
alert(data);
/* now do something with resp */
}
...
I want to return the function when a certain time has passed. In my function below the return thing didn't work(the alert was triggered).
The thing is that in my handleRequestStateChange I initialize myArray.
function() {
xmlHttp.open("POST", "xxx", true);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.setRequestHeader("Content-Length", 10);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(locParam);
setTimeout(function() { return myArray; }, 5000);
alert("end sleep");
}
Thanks!
As you're aware, timers are delayed until later in the thread, but the function that calls them continues to execute straight away. There's no way to delay the currently executing function without locking up the browser and preventing user interaction with the browser.
This is where the point of callbacks come into play. Instead of trying to delay continued execution of code, you supply a function as a parameter to the "delaying" function, and when it has it's necessary data ready, it executes the callback function with the data as a parameter.
function submitRequest(callback) {
xmlHttp.open("POST", "xxx", true);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.setRequestHeader("Content-Length", 10);
xmlHttp.onreadystatechange = function ()
{
if (xmlHttp.readyState == 4 && xml.status == 200)
{
var myArray;
// do something with data
callback(myArray); // execute the callback function
}
};
xmlHttp.send(locParam);
}
function someCallingFunction()
{
// call submitRequest with an anonymous function as the callback parameter
submitRequest(function (myArray)
{
alert("end sleep");
// do something with myArray
});
}
You can put the sleep for certain time, then put the return statement after
the sleep.
So that it will return from the function after some time is passed.