I have a page that chains two API calls, loads the data into first_data and second_data before executing a createPage function (which is several kb of data manipulation and d3.js):
template.html
<script src="createPage.js"></script>
<script>
var first_data, second_data = [], [];
function getFirstData(){
return new Promise(function(resolve) {
var xhr = new XMLHttpRequest();
var url = "/API/my-request?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
resolve('1');
}
}
xhr.open("GET", url, true);
xhr.send();
});
} //similar function for getSecondData()
getFirstData()
.then(getSecondData)
.then(createPage(first_data, second_data));
</script>
The trouble is that some of the code that manipulates the data in createPage is showing errors, for example "can't convert undefined to object". In that particular error's case, it's because I try to do Object.keys(data[0]) on some data that should be loaded from the API requests. Some observations:
If I inspect the data in the browser dev console, it's all there.
If I just paste the code from the file in the console, the page draws fine.
If I hard-code the initializing arrays etc for the data manipulation part of the code (to get rid of the can't convert undefined, then the page draws but all the graphics indicate that they were populated with no data.
The page loads fine if I put the the JSON data in a .js file and load it as a script just before the createPage.js file at the end of the body.
I inserted a console.log("starting") statement at the start and end of createPage(). Looking at the network and js console output when I load, the starting output occurs before the two API GET requests are displayed in the network activity. Is this representative of what's really happening (i.e. can you mix javascript console and network console timing?)
So, clearly I don't have access to the data at the point when I need it.
Why? Are my Promises incorrect?
How can I fix this?
Promise.prototype.then() expects 2 arguments(onFulfilled & onRejected) as function-expression(OR handler or callback) as it is a function(handler) which will be invoked when Promise is fulfilled
In your case, createPage(first_data, second_data) will invoke the function createPage when statement is interpreted by interpreter.
Use anonymous function as an argument and invoke your function inside it.
getFirstData()
.then(getSecondData)
.then(function() {
createPage(first_data, second_data);
});
Edit: If you are not passing any arguments specifically to the callback, you can use .then(FUNCTION_NAME)
In functional programming and using promises, you should probably refactor getFirstData (and getSecondData) to the following form:
function getFirstData(){
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
var url = "/API/my-request?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// Resolve the result, don't assign it elsewhere
resolve(JSON.parse(xhr.responseText));
} else {
// Add rejection state, don't keep the promise waiting
reject("XHR Error, status = ", xhr.status);
}
}
xhr.open("GET", url, true);
xhr.send();
});
}
And then resolve the promises like this (assume first & second data is not dependant on each other)
Promise.all([getFirstData(), getSecondData()]).then(function(allData){
var first_data = allData[0];
var second_data= allData[1];
return createPage(first_data, second_data);
}).catch(function(error){
console.log("Error caught: ", error);
});
To make things even cleaner, you can change createPages's from:
function createPage(first_data, second_data){
// Function body
}
to
function createPage(data){
var first_data = data[0];
var second_data= data[1];
// Function body
}
and the Promise part:
Promise.all([getFirstData(), getSecondData()]).then(createPage);
Notice how short it became?
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'm trying to dynamically load the code of jquery.min.js and then the code of a custom JS file and use the functions from that custom file in my code, outside of the ajax call. Here is the code:
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
eval(this.responseText);
$.post('/ajax.php?action=js', {
'action': 'custom'
}, function (response) {
eval(response);
// the response contains
// function custom1 () {...}
// ...
// function custom200 () {...}
});
}
xhttp.open("POST", "/ajax.php?action=js", true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("action=jquery");
}
// some code here and then calling the function
function somefunc () {
// there is other code here, checking if the ajax response is present
// and then calling the function:
custom1();
}
// calling somefunc(), which checks and calls the "undefined" function
somefunc();
However I get Error: ReferenceError: custom1 is not defined. The issue is most probably not caused by the async, I check if a var created inside the response function is visible here and it is, if it's not setTimeout until it is and then execute the function.
Does somebody know why is that and how can I make the function to be used outside the Ajax call (I checked it in it and there it is defined, but not outside)?
Note that there are some 200 odd functions there with different names I need to use in random parts of the code.
We can reformulate this as a pretty simple fetch() call with promises that ensure the requests have been made before you attempt to call custom1:
function evaluateAjaxActionJs(action) {
// Concoct a form data object with action=<action>
const body = new FormData();
body.append("action", action);
// Do a POST request with the form-data body...
return fetch("/ajax.php?action=js", {
method: "POST",
body,
})
.then((r) => r.text()) // Interpret the result as text
.then((text) => eval(text)); // ... and evaluate it :(
}
// Do both requests in parallel...
Promise.all([
evaluateAjaxActionJs("jquery"),
evaluateAjaxActionJs("js"),
]).then(() => {
// ... and once the requests are done, call `custom1()`.
custom1();
});
Error handling is elided from this example (you'll want to probably add a .catch((err) => ...), as well as check that the fetch returned an OK response (r.ok) before evaluating it.
If you need to first load the jquery thing, then the other, change this to
evaluateAjaxActionJs("jquery")
.then(() => evaluateAjaxActionJs("js"))
.then(() => {
// ...
});
Please forgive me if this is a stupid question. I have been trying for hours and my brain have just stopped working.
I have such system that consists of three AJAX calls. Server response of first call usually is a 200 Success; but second and third queries are fragile because they are image uploading, and on the server side, I have so much validation rules that client's images mostly fail.
window.AjaxCall = function () {
// to pass to $.ajax call later
this.args = arguments;
// xhr status
this.status = null;
// xhr results (jqXHR object and response)
this.xhrResponse = {};
this.dfr = new $.Deferred();
// to provide an easier interface
this.done = this.dfr.done;
this.fail = this.dfr.fail;
this.then = this.dfr.then;
};
AjaxCall.prototype.resetDfr = function () {
this.dfr = new $.Deferred();
};
AjaxCall.prototype.resolve = function () {
this.dfr.resolve(
this.xhrResponse.result,
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.reject = function () {
this.dfr.reject(
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.query = function () {
var _this = this;
// if query hasn't run yet, or didn't return success, run it again
if (_this.status != 'OK') {
$.ajax.apply(_this, _this.args)
.done(function (result, textStatus, jqXHR) {
_this.xhrResponse.result = result;
_this.xhrResponse.jqXHR = jqXHR;
_this.resolve();
})
.fail(function (jqXHR) {
_this.xhrResponse.jqXHR = jqXHR;
_this.reject();
})
.always(function (a, b, c) {
var statusCode = (typeof c !== 'string'
? c
: a).status;
if (statusCode == 200) {
_this.status = 'OK';
}
});
}
// if query has been run successfully before, just skip to next
else {
_this.resolve();
}
return _this.dfr.promise();
};
AjaxCall class is as provided above, and I make the three consecutive calls like this:
var First = new AjaxCall('/'),
Second = new AjaxCall('/asd'),
Third = new AjaxCall('/qqq');
First.then(function () {
console.log('#1 done');
}, function() {
console.error('#1 fail');
});
Second.then(function () {
console.log('#2 done');
}, function() {
console.error('#2 fail');
});
Third.then(function () {
console.log('#3 done');
}, function() {
console.error('#3 fail');
});
var toRun = function () {
First.query()
.then(function () {
return Second.query();
})
.then(function () {
return Third.query()
});
};
$('button').click(function () {
toRun();
});
Those code are in a testing environment. And by testing environment, I mean a simple HTML page and basic server support for debugging.
Home page (/) always returns 200 Success.
/asd returns 404 Not Found for the first 3 times and 200 Success once as a pattern (i.e. three 404s -> one 200 -> three 404s -> one 200 -> three 404s -> ... ).
/qqq returns 404 Not Found all the time.
When I click the only button on the page, first query returns success and second fails as expected. When I click the button second time, first query skips because it was successful last time and second fails again, also as expected.
The problem here is:
before I used the resetDfr method because the dfr is alreay resolved or rejected, it doesn't react to resolve and reject methods anymore.
When I call the resetDfr method in the way I show in the example, dfr is able to get resolved or rejected again, but the callbacks of the old dfr are not binded with the new dfr object and I couldn't find a way to clone the old callbacks into the new dfr.
What would be your suggestion to accomplish what I'm trying to do here?
Promises represent a single value bound by time. You can't conceptually "reuse" a deferred or reset it - once it transitions it sticks. There are constructs that generalize promises to multiple values (like observables) but those are more complicated in this case - it's probably better to just use one deferred per request.
jQuery's AJAX already provides a promise interface. Your code is mostly redundant - you can and should consider using the existent tooling.
Let's look at $.get:
It already returns a promise so you don't need to create your own deferred.
It already uses the browser cache, unless your server prohibits HTTP caching or the browser refuses it only one request will be made to the server after a correct response arrived (assuming you did not explicitly pass {cache: false} to its parameters.
If making post requests you can use $.post or more generally $.ajax for arbitrary options.
This is how your code would roughly look like:
$("button").click(function(){
var first = $.get("/");
var second = first.then(function(){
return $.get("/asd");
});
var third = second.then(function(){
return $.get("/qqq");
});
});
The reason I put them in variables is so that you will be able to unwrap the result yourself later by doing first.then etc. It's quite possible to do this in a single chain too (but you lose access to previous values if you don't explicitly save them.
For the record - it wasn't a stupid question at all :)
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 */
}
...
Is there a way in JavaScript to send an HTTP request to an HTTP server and wait until the server responds with a reply? I want my program to wait until the server replies and not to execute any other command that is after this request. If the HTTP server is down I want the HTTP request to be repeated after a timeout until the server replies, and then the execution of the program can continue normally.
Any ideas?
Thank you in advance,
Thanasis
EDIT: Synchronous requests are now deprecated; you should always handle HTTP requests in an async way.
There is a 3rd parameter to XmlHttpRequest's open(), which aims to indicate that you want the request to by asynchronous (and so handle the response through an onreadystatechange handler).
So if you want it to be synchronous (i.e. wait for the answer), just specify false for this 3rd argument.
You may also want to set a limited timeout property for your request in this case, as it would block the page until reception.
Here is an all-in-one sample function for both sync and async:
function httpRequest(address, reqType, asyncProc) {
var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
if (asyncProc) {
req.onreadystatechange = function() {
if (this.readyState == 4) {
asyncProc(this);
}
};
}
req.open(reqType, address, !(!asyncProc));
req.send();
return req;
}
which you could call this way:
var req = httpRequest("http://example.com/aPageToTestForExistence.html", "HEAD"); // In this example you don't want to GET the full page contents
alert(req.status == 200 ? "found!" : "failed"); // We didn't provided an async proc so this will be executed after request completion only
You can perform a synchronous request. jQuery example:
$(function() {
$.ajax({
async: false,
// other parameters
});
});
You should take a look at jQuery's AJAX API. I highly recommend using a framework like jQuery for this stuff. Manually doing cross-browser ajax is a real pain!
You can use XMLHttpRequest object to send your request. Once request is sent, you can check readyState property to identify current state. readyState will have following different states.
Uninitialized - Has not started loading yet
Loading - Is loading
Interactive - Has loaded enough and the user can interact with it
Complete - Fully loaded
for example:
xmlhttp.open("GET","somepage.xml",true);
xmlhttp.onreadystatechange = checkData;
xmlhttp.send(null);
function checkData()
{
alert(xmlhttp.readyState);
}
hope this will help
For the modern browser, I will use the fetch instead of XMLHttpRequest.
async function job() {
const response = await fetch("https://api.ipify.org?format=json", {}) // type: Promise<Response>
if (!response.ok) {
throw Error(response.statusText)
}
return response.text()
}
async function onCommit() {
const result = await job()
// The following will run after the `job` is finished.
console.log(result)
}
fetch syntax
an examples
<button onclick="onCommit()">Commit</button>
<script>
function onCommit() {
new Promise((resolve, reject) => {
resolve(job1())
}).then(job1Result => {
return job2(job1Result)
}).then(job2Result => {
return job3(job2Result)
}).catch(err => { // If job1, job2, job3, any of them throw the error, then will catch it.
alert(err)
})
}
async function testFunc(url, options) {
// options: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
const response = await fetch(url, options) // type: Promise<Response>
if (!response.ok) {
const errMsg = await response.text()
throw Error(`${response.statusText} (${response.status}) | ${errMsg} `)
}
return response
}
async function job1() {
console.log("job1")
const response = await testFunc("https://api.ipify.org?format=json", {})
return await response.json()
}
async function job2(job1Data) {
console.log("job2")
console.log(job1Data)
const textHeaders = new Headers()
textHeaders.append('Content-Type', 'text/plain; charset-utf-8')
const options = {"headers": textHeaders}
const response = await testFunc("https://api.ipify.org/?format=text", options)
// throw Error(`test error`) // You can cancel the comment to trigger the error.
return await response.text()
}
function job3(job2Data) {
console.log("job3")
console.log(job2Data)
}
</script>
For this you can start loader in javascript as soon as page starts loading and then you can close it when request finishes or your dom is ready.
What i am trying to say, as page load starts, start a loader . Then page can do multiple synchronous request using ajax , until and unless you didn't get response, do not close close loader.
After receiving the desired in response in final call, you can close the loader.
I have a similar situation in an game built with Three.js and Google Closure. I have to load 2 resources, Three and Closure do not allow me to make these synchronous.
Initially I naively wrote the following:
main() {
...
var loaded=0;
...
// Load Three geometry
var loader = new THREE.JSONLoader();
loader.load("x/data.three.json", function(geometry) {
...
loaded++;
});
// Load my engine data
goog.net.XhrIo.send("x/data.engine.json", function(e) {
var obj = e.target.getResponseJson();
...
loaded++;
});
// Wait for callbacks to complete
while(loaded<2) {}
// Initiate an animation loop
...
};
The loop that waits for the callbacks to complete never ends, from the point of view of the loop loaded never get incremented. The problem is that the callbacks are not fired until main returns (at least on Chrome anyway).
One solution might be to have both callbacks check to see if it's the last to complete, and them go on to initiate the animation loop.
Another solution - perhaps a more direct answer to what you are asking (how to wait for each load before initiating another) - would be to nest the callbacks as follows:
// Load Three geometry
var loader = new THREE.JSONLoader();
loader.load("x/data.three.json", function(geometry) {
...
// Load my engine data
goog.net.XhrIo.send("x/data.engine.json", function(e) {
var obj = e.target.getResponseJson();
...
// Initiate an animation loop
...
});
});
};
This is an old question but wanted to provide a different take.
This is an async function that creates a promise that resolves with the Http object when the request is complete. This allow you to use more modern async/await syntax when working with XMLHttpRequest.
async sendRequest() {
const Http = new XMLHttpRequest();
const url='http://localhost:8000/';
Http.open("GET", url);
Http.send();
if (Http.readyState === XMLHttpRequest.DONE) {
return Http;
}
let res;
const p = new Promise((r) => res = r);
Http.onreadystatechange = () => {
if (Http.readyState === XMLHttpRequest.DONE) {
res(Http);
}
}
return p;
}
Usage
const response = await sendRequest();
const status = response.status;
if (status === 0 || (status >= 200 && status < 400)) {
// The request has been completed successfully
console.log(response.responseText);
} else {
// Oh no! There has been an error with the request!
console.log(`Server Error: ${response.status}`)
}
For those using axios, you can wrap it in an async iife and then await it:
(async () => {
let res = await axios.get('https://example.com');
// do stuff with the response
})();
Note, I haven't done any error checking here.