Return result of XMLHttpRequest - javascript

I'm trying to return the result of an XMLHTTPRequest:
Click me for Google CDN jQuery!
<script>
const url = {
httpRequest: function(callback) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", callback);
xhr.open("GET", document.querySelector("a").href); // Using querySelector to simulate how my real program works - I don't want to hardcode anything mainly because this should be dynamic.
xhr.send("");
},
compileData: function(data) {
var response = data.target.responseText.substring(4, 17)
// I can't figure out how to take this response and 'return' it.
},
getContent: function() {
url.httpRequest(url.compileData)
}
}
var content = url.getContent() // I want 'content' to be equal to "jQuery v3.3.1"
</script>
But, I can't figure out how to 'return' the response.
Yes, I know that there are other questions out there like this one, namely: How do I return the response from an asynchronous call? But, I'm new to JavaScript and have no idea how to integrate what they're saying there into my case.

A few options for applying the guidance given in "How do I return the response from an asynchronous call?"
If you'd like to continue using callbacks, then it's a matter of adding another. The result you'll want is to allow your entry point to accept its own:
url.getContent(function (content) {
// proceed with `content` here
});
So, getContent needs to expect the argument:
getContent: function(callback) {
// ...
}
But, it also needs to call the callback, which it can do with yet another intermediate function, so it can combine callback with compileData:
getContent: function(callback) {
url.httpRequest(function (data) {
callback(url.compileData(data));
});
}
You can also use Promises throughout the url object.
Starting with httpRequest, you can utilize jQuery.ajax() and its shortcuts returning a customized promise object – a jqXHR:
httpRequest: function () {
return Promise.resolve(
jQuery.get(document.querySelector("a").href)
);
}
In this, Promise.resolve() can be used to convert the customized promise into a standard Promise.
Then, getContent can be modified to interact with the promise, modifying the response with compileData:
getContent: function () {
return url.httpRequest().then(url.compileData);
}
And, the caller can add its own handler using the Promise's methods:
url.getContent().then(function (content) {
// proceed with `content` here
});
Also, a brief aside... In supporting browsers, you can use fetch() in place of jQuery.ajax(), which already returns a standard Promise, but will first give you a Response object rather than the raw data.
httpRequest: function () {
return fetch(document.querySelector("a").href)
.then(response => response.text())
}

There are three different ways according to 3 JS standards i.e es5,es6 and es7 but here you have used xhr therefore it is an old standard.
Hence, you should use xhr.onload method of xhr object over here to return the response. Simply do:
xhr.onload = callback;
insert this code between xhr.open and xhr.send.

You need Promises, Promises and more Promises! And you should return one from your function.
function httpRequest() {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.onload = (response) => {
resolve(response); // You should check the response and maybe preprocess it
// so that not everything is passed further
}
xhr.open("GET", document.querySelector("a").href);
xhr.send("");
});
}
and then you may use then and catch
httpRequest().then((data) => {
// Yay! Got response from the server!
}).catch((error) => {
// Yay! More errors!
});

Related

How to store XMLHttpRequest response as variable [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
async/await implicitly returns promise?
(5 answers)
Closed 1 year ago.
In the below example from https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/onreadystatechange, I am trying to store the responseText in a variable that can be accessed outside of the onreadystatechange function.
const xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/";
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
// In local files, status is 0 upon success in Mozilla Firefox
if(xhr.readyState === XMLHttpRequest.DONE) {
var status = xhr.status;
if (status === 0 || (status >= 200 && status < 400)) {
// The request has been completed successfully
console.log(xhr.responseText);
} else {
// Oh no! There has been an error with the request!
}
}
};
xhr.send();
At the moment, the only way I know to handle the return value from a function is to do the following
const variable = function()
But I can't figure out how to make that won't work due to the way the onreadystatechange event handler is initiated/called. I tried declaring a variable outside of the function scope, but it keeps returning 'undefined'.
Thanks for your help.
*Edit for async/await with fetch
I've been trying to implement the async/await fetch option but can't seem to get it to work right. I've been checking the output with alerts and the alert located outside the function always fires before the one inside the function. As a result the one outside is giving me "object Promise" and the one inside is the actual data. I'm returning the data but it's just not working.
const getData = async function (filePath) {
let myData
await fetch(filePath)
.then(response => response.text())
.then(data => myData = data)
alert(myData)
return (myData)
}
let theData = getData('./2021WeatherData.csv')
alert(theData)
So "alert(theData)" fires before "alert (myData)" and I can't understand why. Thanks so much.
This is a good question.
The XMLHttpRequest is code that runs asynchronous. And it can't run synchronously as the yellow warning on the Mozilla developer documentation explains.
So what does that mean?
(Pseudocode)
var result
console.log('start')
xhr.onreadystatechange = function() {
result = '5'
console.log('request done')
}
console.log('result', result)
This will output as you already noticed
start
result undefined
request done
and you are expecting:
start
request done
result 5
But this does not work, because the console.log('result', result) was
already executed before the variable was assigned.
Solutions
Using a callback
Whatever you want to do with the result, you need to do within the callback function. Like:
Console.log the value
Updating the browser dom (I added this as an example)
Store to the database on a node server
The callback function is the function you are assigning to the onreadystatechange.
(Pseudocode)
xhr.onreadystatechange = function() {
// This code is the callback function.
// You can do everything with the result, like updating the DOM,
// but you can't assign it to a variable outside.
}
Actual code
function logResultToTextBox(result) {
document.getElementById('#server-result-text-box').value = result
}
const xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/";
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
// In local files, status is 0 upon success in Mozilla Firefox
if(xhr.readyState === XMLHttpRequest.DONE) {
var status = xhr.status;
if (status === 0 || (status >= 200 && status < 400)) {
// The request has been completed successfully
console.log(xhr.responseText);
// DO WHATEVER YOU NEED TO DO HERE WITH THE RESULT, BUT YOU CANT ASSIGN THE VALUE TO VARIABLE WHICH IS DECLARED OUTSIDE
logResultToTextBox(xhr.responseText);
} else {
// Oh no! There has been an error with the request!
}
}
};
xhr.send();
Wrap it using a promise
The promise is another way on how you can solve this. You wrap it into a promise function. Not that the promise has no return statement. It uses a resolver.
The actual work will then be done after you define the functions. It will be called in the then block.
Here you also can't assign a value to a variable that is declared outside.
Pseudocode:
Promise makeRequest(url) {
// doRequest to url and resolve result
}
makeRequest('http://example.com).then((result) => {
// do whatever you want with the result
})
Actual code:
function logResultToTextBox(result) {
document.getElementById('#server-result-text-box').value = result
}
function makeRequest (method, url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.send();
});
}
// Example:
makeRequest('GET', 'https://developer.mozilla.org/')
.then(function (result) {
console.log(result);
logResultToTextBox(result)
})
.catch(function (err) {
console.error('Augh, there was an error!', err.statusText);
});
Use async / await
For a few years, there is also a new syntax called async/await. This gives you the possibility to write code like it would a normal code.
Pseudocode
let myServerResult;
async function doAsyncCall(url) {
const serverResult = await callServer(url);
return serverResult
}
myServerResult = await doAsyncCall('http://example.com')
console.log(myServerResult)
In that case, I would use fetch instead of XMLHttpRequest because it is much simpler.
Actual code:
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const jsonResult = await response.json();
console.log(jsonResult)
(note that I am using jsonplaceholder.typicode.com as an API as the HTML output of the Mozilla documentation does not work with it. If you really need XMLHttpRequest then have a look into the answers on this question)
P. S. Don't use old synchronous code syntax
There is also an old-school way by using "Synchronous request". But this should not be used anymore, as it leads to a bad user experience because it makes the loading of the website slow.
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests#synchronous_request
Answer after the question edit
You need to await the following line
instead of
let theData = getData('./2021WeatherData.csv')
you need to write
let theData = await getData('./2021WeatherData.csv')

Leaving jQuery, wrote a simple ajax function, but chained methods will not wait

Update: Added a simpler demonstration jsfiddle, https://jsfiddle.net/47sfj3Lv/3/.
reproducing the problem in much less code I'm trying to move away from jQuery.
Some of my code, for populating some tables, has code like this
var hb = new hbLister(url: '#attributes.listURL#')
.getData(#url.page#, #url.per#)
.search(searchColumn, searchParam)
.render();
hbLister would initialize some things
getData would perform an $.ajax call
search wouldconsole.log('filtering data') and apply the search conditions against a javascript object
render would put the results on the page.
Importantly, search wouldn't fire until after the ajax call in getData finished.
So, now I have this ajax constructor. I've abbreviated as much of this code as I can.
let ajax = function (options, hooks, headers) {
let that = this;
// enforce parameter types
// copy parameters to this.base
this.base = { options: options, hooks: hooks, headers: headers }
return function (url, options, data, hooks, headers) {
// enforce variable types
// merge options and hooks with their base.counterparts
headers = new Headers(Object.assign({}, that.base.headers, headers));
options.headers = headers;
return fetch(url, options)
.then(response => {
return response.json().then(json => {
console.log('processing');
if (response.ok) {
// it's omitted here but the Base functions are defined
// in the constructor parameters
hooks.successBase(json, response.status, response.headers);
hooks.success(response.json, response.status, response.headers)
} else {
hooks.failureBase(json, response.status, response.headers);
hooks.failure(response.json, response.status, response.headers)
}
})
});
}
}
The idea is that I can say
let tableLister = new ajax()
And thengetData can call
tableLister = tableLister(hb.url, // url
{ type: "GET" }, // options
config.data, // data
{ success: successFn } // hooks, success being the callback
)
The jQuery call would properly give me and then processing and then filtering data.
This function gives me filtering data, an error, and thenprocessing, because I cannot seem to get the chain(.search(...).render()) to wait til the ajax call is done.
Here's a self-contained example on jsFiddle, https://jsfiddle.net/47sfj3Lv/3/
I am sure the answer is in async and await, but I have not been able to get it to work.
Here is an example of what I've tried
return await (async function () {
console.log('fetching')
let fetcher = await fetch(url, options);
console.log('getting json');
return await fetcher.json().then((json) => {
console.log('have json, doing hooks');
if (fetcher.ok) {
let argXHR = { json: json}
hooks.successBase(argXHR, hooks.params);
hooks.success.forEach(v => v(argXHR, hooks.params));
hooks.afterSend(argXHR, hooks.params);
} else {
let argXHR = { json: json,}
hooks.failureBase(argXHR, hooks.params);
hooks.failure.forEach(v => v(argXHR, hooks.params));
hooks.afterError(argXHR, hooks.params);
}
console.log('finished hooks')
})
}())
And no matter what I do, the chain, continues before this await finishes..
I got code with XMLHttpRequest to work. The method chain (.getData().search(...).render()) works with this, because this doesn't allow the ajax function to return before the request is finished and callbacks are executed. **I'd still prefer to make .fetch() work.
let xhr = new XMLHttpRequest()
let urlParams = [];
Object.keys(data).forEach((v) => urlParams.push(v + '=' + encodeURIComponent(data[v])));
urlParams = urlParams.join('&')
xhr.open(options.method, options.url, false);
xhr.onreadystatechange = function(state) {
if (this.readyState == 4) {
let json = JSON.parse(xhr.responseText)
hooks.successBase(json, xhr.status, xhr.getResponseHeader);
hooks.success.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
}
xhr.onerror = function() {
let json = JSON.parse(xhr.responseText)
hooks.failureBase(json, xhr.status, xhr.getResponseHeader);
hooks.failure.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
for (h in headers) {
xhr.setRequestHeader(h, headers[h])
}
xhr.send(urlParams)
This was difficult for me to understand, so I wanted to share if anyone else has the same issue.
It seems that an async method will break a method chain, there's no way around that. And since fetch is asynchronous, await must be used, and in order for await to be used, the calling method must be declared async. Thus the method chain will be broken.
The way the method chain is called must be changed.
In my OP, I linked https://jsfiddle.net/47sfj3Lv/3/ as a much simpler version of the same problem. StackOverflow's 'fiddle' effectively blocks 'fetch' for security reasons, so I need to use JSFiddle for demonstration.
Here's a working version of the same code using then and how/why it works, and a slightly shorter version, because await can be specified with the the fetch, obviously.
let obj = {};
// methods with fetch ideally should be specified async.
// async calls will automatically return a promise
obj.start = async () => {
console.log('start begins')
let retText = "",
fetcher = fetch('/', {}).then(response => response.text())
.then(text => {
console.log('fetch\'s last then')
retText = text
})
// fetcher has just been declared. It hasn't done anything yet.
console.log('fetch requested, returned length:', retText.length)
// this makes the fetcher and sequential then's happen
await fetcher;
console.log('await let fetch finish, returned length:', retText.length)
// Async functions will return a promise, but the return specified here
// will be passed to the next 'then'
return obj
}
obj.middle = () => {
console.log('middle called')
// Because this is not declared async, it behaves normally
return obj;
}
obj.end = () => {
console.log('end called')
// Because this is not declared async, it behaves normally
}
console.log('Method 1: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle())
// Middle is not Async, but since it's wrapped in then
// it returns a promise
.then((step) => step.end())
)
// This is just wrapped in a timer so the two logs don't intermix with each other
// This is definitely the preferred method. Non-async chain-methods that return
// a reference to their object, do not need to wrapped in then().
setTimeout(function() {
console.log('------------------------')
console.log('Method 2: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle().end())
)
}, 3000)

Can't promisify callback based function

I want to use the library astro-js where a typical call in their docs looks like this:
const aztroJs = require("aztro-js");
//Get all horoscope i.e. today's, yesterday's and tomorrow's horoscope
aztroJs.getAllHoroscope(sign, function(res) {
console.log(res);
});
For several reasons, I would like to use it using async/await style and leverage try/catch. So I tried promisify like this:
const aztroJs = require("aztro-js");
const {promisify} = require('util');
const getAllHoroscopeAsync = promisify(aztroJs.getAllHoroscope);
async function handle() {
let result, sign = 'libra';
try {
result = await getAllHoroscopeAsync(sign);
}
catch (err) {
console.log(err);
}
console.log("Result: " + result);
}
However, when I log result it comes as undefined. I know the call worked since the library is automatically logging a response via console.log and I see a proper response in the logs.
How can I "await" on this call? (even by other means if this one is not "promisifyable")
util.promisify() expects the callback function to accept two arguments, the first is an error that must be null when there is no error and non-null when there is an error and the second is the value (if no error). It will only properly promisify a function if the callback follows that specific rule.
To work around that, you will have to manually promisify your function.
// manually promisify
aztroJs.getAllHoroscopePromise = function(sign) {
return new Promise(resolve => {
aztroJs.getAllHoroscope(sign, function(data) {
resolve(data);
});
});
};
// usage
aztroJs.getAllHoroscopePromise(sign).then(results => {
console.log(results);
});
Note, it's unusual for an asynchronous function that returns data not to have a means of returning errors so the aztroJs.getAllHoroscope() interface seems a little suspect in that regard.
In fact, if you look at the code for this function, you can see that it is making a network request using the request() library and then trying to throw in the async callback when errors. That's a completely flawed design since you (as the caller) can't catch exceptions thrown asynchronously. So, this package has no reasonable way of communicating back errors. It is designed poorly.
Try custom promisified function
aztroJs.getAllHoroscope[util.promisify.custom] = (sign) => {
return new Promise((resolve, reject) => {
aztroJs.getAllHoroscope(sign, resolve);
});
};
const getAllHoroscopeAsync = util.promisify(aztroJs.getAllHoroscope);
You could change your getAllHoroscopeAsync to a promise function
Example:
const getAllHoroscopeAsync = (sign) =>
new Promise(resolve =>
aztroJs.getAllHoroscope(sign, (res) => resolve(res)));

Function that returns the contents of a text file JavaScript

I am trying to create a function that returns the contents of a txt file using JavaScript so it can be stored in a variable and used at a later time. So far I have tried:
var text;
function getText(url) {
fetch(url)
.then( r => r.text() )
}
getText("foo.txt").then(r => text);
but this says that text is undefined.
I have also tried:
function getText(url) {
var client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = function() {
text = client.responseText;
return text;
}
client.send();
}
This still gives me undefined. If I put a console.log() inside the readystatechange it gives the right text but repeats it twice.
I have also tried:
function getText(url) {
jQuery.get(url, function(data) {
console.log(data);
});
return data;
}
but this gives me an error saying jquery is not defined and I can't seem to get jQuery to work so preferably is there anyway to do this without jquery?
The first approach using the fetch API is rightly returning undefined as you are not returning the thenable. Note that the return value itself is a promise and not the data, you need to call then() to log the data. Currently there is no way to assign the returned data directly to a variable.
As for the second example if you put the console.log in the client.onreadystatechange of course it will log the response twice as it gets called multiple times for a single request. You should only log the response when the response code is 200 OK and state is complete with a value of 4.
function getText(url) {
return fetch(url) //notice the return, it is returning a thenable
.then(text => text.text())
}
getText("https://jsonplaceholder.typicode.com/todos/1").then(d => console.log(d));
//Using Async/Await
async function getTextAsync(url) {
let text = await fetch(url)
let data = await text.text()
return data;
}
getTextAsync("https://jsonplaceholder.typicode.com/todos/1").then(d => console.log(d));
//Using XMLHttpRequest
function getTextXml(url) {
var client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = function() {
if (client.readyState == 4 && client.status == 200) //notice the response code and the state code
{
if (client.responseText)
{
console.log(client.responseText);
}
}
}
client.send();
}
getTextXml("https://jsonplaceholder.typicode.com/todos/1");
console.log() just prints it to the console, is there anyway to store it in the variable?
Because fetch is an async process you will never be able to assign a value to text like that. The closest you can get with modern browsers at the moment is to use async/await.
// Mock `fetch` to return a value after 1.5s
function getText(url) {
return new Promise(resolve => {
setTimeout(() => resolve('Hallo'), 1500);
});
}
// Unfortnately `await` has to be used in conjunction
// with async so we have to use an immediately-invoked
// async function expression here
(async () => {
// `await` for the promise to resolve
const text = await getText("foo.txt");
console.log(text);
})();
<p>Please wait 1.5s</p>
There's a lot more information (which will take a while to distill) in the answers to this Stackoverflow question.

Loading configuration async in JavaScript, how best to wait for response?

This is what I am trying to do:
Request a JSON object from remote server script
WAIT until the JavaScripts gets all the response data
print a value from the response object
I'm trying to use setTimeout in the get config function to recall itself after 1 second if the value is undefined, when it's no longer undefined do something with the value.
Alternatively it seems I could just create some method that loops for some number of seconds, but i'd like to avoid that if there is a better way to accomplish what i'm trying to do? My current code just seems to recurse without any delay which breaks my runtime
Thanks for any ideas, heres the code:
function runApplication() {
var initialiser = new Initialiser();
var config = new Config();
initialiser.fetchConfigurations(config);
config.alertValue('setting1');
}
function Initialiser() {
debug.log("Started");
}
Initialiser.prototype.fetchConfigurations = function(config) {
var req = new XMLHttpRequest();
var url = CONFIGURATION_SERVER_URL;
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) {
var configObject = eval('(' + req.responseText + ')');
config.setConfig(configObject);
} else {
debug.log("Downloading config data...please wait...");
}
}
req.open("GET", url, true);
req.send(null);
}
function Config() {
this.config
}
Config.prototype.setConfig = function(configObject) {
this.config = configObject;
}
Config.prototype.getValue = function(setting) {
if(this.config === undefined) {
setTimeout(this.getValue(setting), 1000);
} else {
return this.config[setting];
}
}
Config.prototype.alertValue = function(setting) {
if(this.config === undefined) {
setTimeout(this.alertValue(setting), 1000);
} else {
alert(this.config[setting]);
}
}
From what I'm looking at, you should just add an extra step to .setConfig, which handles the next piece.
Config.prototype.setConfig = function (data) {
this.config = data;
this.doSomethingAfterTheDataExists();
};
There are lots of ways to do this...
...writing a moderator/observer/pub-sub implementation is dirt-simple, and could be done in relatively few lines for the benefit given...
Writing a Promise system would be even more powerful, but would require more work (because it's like the above implementations, with an added layer of abstraction, to make a really clean and straightforward interface for async).
The other alternative to having .setConfig publish a message, or fire another method, would be to call another method, after .setConfig in the AJAX call.
xhr.onreadystatechange = function () {
// .......
config.setConfig(/* ... */);
config.doSomethingAfterTheDataExists();
};
If you're making multiple AJAX calls, with multiple assignments to multiple properties, and you want to wait until every single call is done, then you really should look into implementing a Promise system.
It will save your sanity and would be well-worth the ~60-100 lines which you could minify into oblivion.
If you're just waiting for that one response to come back, then keep in mind, what happens after the onreadystatechange says that it's finished and the data's there (your success check) is synchronous.
The waiting is the async part.
Once it's delivered, everything (including the stuff inside of that check), goes back to being procedural.
Just use jQuery and getJSON with success/done callback.
You could use a synchronous call instead of asynchronous call for XMLHttpRequest. But this may not be the best option if your your users need to interact with the web page while you wait for a response.
...
req.open("GET", url, false);
request.send(null);

Categories

Resources