Why Promise return an empty string? - javascript

I have a function foo which makes an Ajax request.
I tried returning the data from the callback and got the data with "xhr.onload" successfully but got an empty string("") with "xhr.onreadystatechange".
Could anyone tell me why??
Thank you very much!
function foo(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open("GET",url,true);
// xhr.onload = function(){
// if(xhr.status == 200){
// resolve(xhr.responseText);
// }else{
// reject("false")
// }
// }
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState && xhr.status == 200){
resolve(xhr.responseText);
}else{
reject("false")
}
}
})
}
foo(url).then(function (data){
console.log(data)
},function (err){
console.log(err)
})

Your onreadystatechange handler is incorrect. You need to check readyState for the value 4 (not just any truthy value), and you don't want to reject until readyState is 4:
if(xhr.readyState === 4){
if (xhr.status == 200) { // or xhr.status >= 200 && xhr.status < 300
resolve(xhr.responseText);
} else {
reject("false")
}
}
But with modern browsers, you'd probably use fetch instead, which already provides a promise. Just be sure not to make these common mistakes (that's a post on my anemic little blog).
As for why you were seeing what you were seeing, since you called open and send before attaching the handler apparently you didn't get the callback for readyState 1 (opened), so it looks like the first callback you got was when the headers were received (readyState 2), at which point xhr.status would be set — so you were resolving your promise, but of course, the request body hadn't been received yet.

Related

How to make callback run asynchronously?

I have a JavaScript function called getAandB which takes a callback. getAandB firstly gets value 'a' using ajax. It then invokes the callback with value 'a' as an argument. The callback gets value 'b' and console.logs both 'a' and 'b' to the console. so I get {"key":"a"} {"key":"b"} in the console.
I thought that the two ajax calls would happen simultaneously / asynchronously. However, they seem to run one after the other ie. synchronously.
The JavaScript code and the PHP code for the ajax requests is shown below:
index.html:
<script>
function getAandB(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
callback(xhr.responseText)
}
}
xhr.send();
}
function callback(resultA){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
const resultB = xhr.responseText;
console.log(resultA, resultB);
}
}
xhr.send();
}
getAandB(callback);
</script>
ajax-a.php:
<?php
sleep(5);
$response = [
"key" => "a",
];
echo json_encode($response);
The code for ajax-b.php is the same as for ajax-a.php except the value of $response.key is b not a.
I thought that the above code would result in ajax calls being made simultaneously to get 'a' and 'b'. However if the PHP code sleeps for 5 seconds for both ajax-a.php and ajax-b.php, then it takes 10 seconds for the console.log to appear. If only one of the ajax-?.php scripts sleeps for 5 seconds then it takes 5 seconds for the console.log to appear.
How can I use callbacks to allow me to combine the results of ajax calls, as I have done here, but to make the individual calls happen simultaneously / asynchronously? Alternatively, is not possible to implement this with callbacks?
If you want the request to ajax-b to me made at approximately the same time as the request for ajax-a then you need to make the respective calls to xhr.send() at approximately the same time.
At the moment, the call to ajax-b's send() takes place as part of callback() which you only call after you have received the response to the request for ajax-a.
You then need to add additional logic to determine when you have received both responses so you log both bits of data at the same time (assuming you still want to do that).
A rough and ready way to do that, keeping to your current approach, would look something like this:
function getA(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
callback(xhr.responseText)
}
}
xhr.send();
}
function getB(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
const resultB = xhr.responseText;
callback(xhr.responseText)
}
}
xhr.send();
}
function getAandB() {
const data = [];
function callback(responseData) {
data.push(responseData);
if (data.length === 2) {
console.log(...data);
}
}
getA(callback);
getB(callback);
}
getAandB();
We have better tools for that these days though, thanks to promises and modern APIs (like fetch) which support them natively.
async function getAandB() {
const dataPromises = [
fetch("./ajax-a.php").then(r => r.text()),
fetch("./ajax-b.php").then(r => r.text())
];
const data = await Promise.all(dataPromises);
console.log(...data);
}
getAandB();
I tried to edit my question but 'the edit queue was full'.
It took me a while to understand #Quentin's answer but I finally realized it relies on the fact that both instantiations of the callback function are altering the same variable (I think that is called by reference and is the default situation with arrays). Given this, although the instantiations know nothing about each other, it is possible to know when both ajax calls have completed by checking to see if the data array has been updated twice. If it has then both must have completed and data can be consoled out.
There is no need for the getAandB function. This much simpler and less confusing code works exactly the same as Quentin's answer:
<script>
const data = [];
function getA(){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
data.push(xhr.responseText);
if (data.length === 2){
console.log(...data);
}
}
}
xhr.send();
}
function getB(){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
data.push(xhr.responseText);
if (data.length === 2){
console.log(...data);
}
}
}
xhr.send();
}
getA();
getB();
</script>

Ajax response in case of failing

I'm using ajax to send data to backend.
My issue is that until the result is returning from the server I get the fail message, and after that Success Message.
I presume that it happens, because it goes to else until a response is received. How can I avoid this issue.
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
setResponse(JSON.parse(xhr.responseText).message);
}
else if(xhr.status !== 200) {
setResponseMessage('Form Failed.Please contact support.')
}
};
Your logic is wrong in the onreadystatechange callback, it should be
else if(xhr.readyState === 4 && xhr.status !== 200)

Promise is rejected prematurely multiple times before resolving

I'm making a call to an API and then trying to render a chart of the data returned:
function getFromAPI(url) {
return new Promise( (resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log("Resolving!");
jsonData = JSON.parse(xhr.responseText);
resolve(jsonData);
} else {
console.log("Rejecting!");
reject();
}
}
xhr.open("GET", url, true);
xhr.send();
});
}
getFromAPI(API_URL).then( (jsonData) => { drawChart(jsonData) });
When I load that script I get Rejecting! three times in the console before it resolves. The reject is also breaking the .then part (i.e. no chart for me!)
I take it the onreadstatechange event is firing a few times before we get to readyState == 4 and status == 200. What exactly is going on and how do I avoid rejecting the promise prematurely?
The onreadystatechange is an event handler that is fired whenever the xhr readyState changes.
0 UNSENT Client has been created. open() not called yet.
1 OPENED open() has been called.
2 HEADERS_RECEIVED send() has been called, and headers and status are available.
3 LOADING Downloading; responseText holds partial data.
4 DONE The operation is complete.
As you can see there are 4 "not ready" states, and one "done". This accounts for the console log of reject and resolve that you see.
When the request is "done", reject or resolve the request according to the status:
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) {
return;
}
if (xhr.status === 200) {
console.log("Resolving!");
jsonData = JSON.parse(xhr.responseText);
resolve(jsonData);
return;
}
console.log("Rejecting!");
reject();
}

DOM exception 11

I'm running this code via the console on http://TheScoutApp.com on line 3 I'm getting a DOM exception 11!!!
var xhr2 = new XMLHttpRequest();
xhr2.onreadystatechange = function() {
console.error(xhr2.statusText); //DOM exception 11!!!
if (xhr2.readyState === 4 && xhr2.status === 200) {
console.error('xhr2');
}
}
xhr2.open("GET","http://thescoutapp.com/extension/update.xml",true);
xhr2.send();
The property xhr.statusText may only be accessed after the request is finished. But the onreadystatechange-callback gets called erlier - the earlier calls have xhr.readyState==1 (=server connection established).
You have to put the assess of xhr.statusText inside a condition:
if(xhr.readyState == 4) {
console.error(xhr.statusText);
}

Passing a return value

Please help, I have been looking at this all day and I know there must be a simple fix!
How do I pass results back to textService so that I can make a call such as
textResult = textService(text to pass in);
I don't want to use a global variable if I can avoid it.
This is the code
function textService(text){
req.open("GET", "http://....?text="+text, true);
req.onload = showResults;
req.send(null);
}
function showResults() {
results = req.responseXML.getElementsByTagName("Result");
}
Thank you in advance
function textService(text){
// set flag to false for sync requests
req.open("GET", "http://...?text="+text, false);
req.send(null);
// browser will be stalled till request is complete.
if(req.status == 200 && req.readyState == 4) {
return req.responseXML.getElementsByTagName("Result");
} else {
return 'request failed';
}
}
// javascript will stall till request is complete.
var results = textService('someText');
Note, making synchronous request can be harmful, if a request fails, it might stall the browser indefinatly. It's better to do it asynchronously.
function textService(text, callback){
// async is true by default, no need to pass 3rd param.
req.open("GET", "http://...?text="+text);
req.send(null);
req.onreadystatechange = function(){
if(this.readyState == 4 || this.status == 200) {
callback(this.responseXML);
}
}
}
textService('someText', function(xml){
// do stuff with XML.
});
Just need switch your coding mind to async programming ;)
You can use this
function showResults() {
results = this.responseXML.getElementsByTagName("Result");
}

Categories

Resources