How to make asynchronous function work like a regular one? - javascript

I have a code that XMLHttpRequest. It's a object to send request to server and it's asynchronous so when I want to receive the response I need to provide the callback function to onreadystatechange property of this object. And this function is called after response being received:
function send()
{
XMLHttpRequest req = new...
req.onreadystatechange = answer;
}
function answer()
{
//handling the answer...
}
So it's great but I don't want to use new function to handle answer so I do it anonymous:
function send()
{
XMLHttpRequest req = new...
req.onreadystatechange = function ()
{
//handling the answer...
};
}
But now i want to use the result of send function in another function for example to display result:
display(send())
So how to make this work? Something like:
function send()
{
XMLHttpRequest req = new...
req.onreadystatechange = function ()
{
//handling the answer...
return result; //where result is returned by send function
};
}
Is there any way to do this so that other JS code will still work while this code will handle the response?

With asynchronous functions you need to do it the opposite way. Instead of saying display(send()) you need to sue send(display)
function send(callback)
{
XMLHttpRequest req = new...
req.onreadystatechange = function ()
{
//handling the answer...
callback(result);
};
}
Trying to write display(send()) won't work. The send() function produces a value asynchronously but returns promptly. It can't return the result of the async operation.

Inside the readystatechange handler, the response is available on req.responseText. You should put your call to display() inside that event handler.

You can use synchronous AJAX call:
XMLHttpRequest req = new...
var result = null;
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.status == 200) {
result = req.responseText;
}
}
};
req.open('GET', url, false); //async = false
return result;
But this is a poor practice and not an idiomatic way of working with JavaScript. Quoting Synchronous and asynchronous requests:
Note: You shouldn't use synchronous XMLHttpRequests because, due to the inherently asynchronous nature of networking, there are various ways memory and events can leak when using synchronous requests.
You know how to use callbacks, why not simply:
send(display)

Related

Is it bad practice to use a global XMLHttpRequest?

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
}
}

Returning data resolved by XMLHttpRequest with module pattern function

I have problem combining javascript callbacks and revealing module pattern.
What I'm trying to do is to return the HTTP response text with the carsData.getCars() function method.
Basically what I want to do is:
return the data from xhr.onreadystatechange function to the private getData function
return the data from getData function to the public getCars function ( or call the getCars function returning a value)
I got it to work with the synchronous AJAX mode, but I'm aware it's not the best practice in javascript development.
I tried to get it to work with callbacks but failed miserably.
Is it even posible to do in javascript?
P.S. I use XMLHttpRequest in Vanilla JS instead of other frameworks for learning purposes.
'use strict';
var carsData = (function() {
var carsElement = document.getElementById('carsListing'),
successHandler = function(data) {
carsElement.innerHTML = data.data;
//return data;
},
dataWrapper = "",
getData = function(callback) {
var url = 'data/cars.json',
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
var status;
var data;
if (xhr.readyState == 4) { // `DONE`
status = xhr.status;
if (status == 200) {
data = JSON.parse(xhr.responseText);
successHandler && successHandler(data);
callback(data);
return data;
}
}
};
xhr.open('get', url, false); // synchronous js
xhr.send();
return xhr.onreadystatechange();
//return 'xx';
}
return {
getCars: function() {
return getData(function(data){
console.log(data); // Object {data: Array[5]}
})
}
}
})();
No. You cannot do it this way. I figured out that is why you typically see results sent to a DOM object. Because they are there waiting for the answer. Your return statement, as counter-intuitive as it seems (assuming you are coming from non-prototype languages), will have already run. It seems like it wouldn't but it has because of the async nature you are aware of. You have to use Promises or you have to have your callback doing something with the data that is "waiting" for the callback data like you did with successdata.

I would like to execute regular function in async way

Consider this sample (say this is module)
function Calculator(value){
return {
add: function(value2){
return: {
value: function(){
return value + value2;
}
}
}
}
}
This is a class, and requires an argument when initialization, sample usage:
var Calculator = require('calculator_app_module');
var myCalc = new Calculator(1); // initialized with 1
myCalc.add(2).value(); // === 3;
Which is obviously expected, what i want is to execute add function in async way, just like that
var Calculator = require('calculator_app_module');
var myCalc = new Calculator(1); // initialized with 1
myCalc.add(2).value() ==== 3 // this executes in 2secs (async)
// and then returns result
I would like to patch Calculator.add method so that it can work with async
function patch(module){ //module is Calculator class
var oldAdd = Calculator.add;
Calculator.add = function(){
// some magic
// trigger event or whatever
oldAdd.apply(Calculator, arguments);
}
}
INDEX.JS
var Calculator = require('calculator_app_module');
var calc = new Calculator(1);
calc.add(2).value() === 3; // equalize within 2 seconds
// after async call is done
calc.add(2).value().equal(3); // also legit
The problem is that calc.add(n) returns new function value which is undefined in async call, is there a way to get the calling fn of add and call it back when result comes
update
Prior to #Zohaib Ijaz answer, you cannot modify content/logic of package, only extend/patch, Package must return same API but in promise way, no code breaking
calc.add(2).value() === 3; // sync code
calc.add(2).value() === 3; // async code
calc.add(2).value().equal(3); // async code
How to achieve
update
According to #Zohaib Ijaz comment, this also legit
myCalc.add(2).value().equal(3); //async
Point is in converting sync to async without breaking package, but extending the outcome
If you request a result by calling a chain of methods, like this:
a = myCalc.add(2).value();
or this:
myCalc.add(2).value().equal(3);
then there is no possibility to retrieve and use results that become available only asynchronously (i.e. later, after the statement has been evaluated). Note that asynchronous involves some event being put in the event queue. The currently executing code must finish first (i.e. until the call stack is empty), before that event can get processed.
The above syntax is useful for immediate evaluation only. In order to process asynchronous results you need to provide a call-back function somewhere for being informed about those results.
So with an asynchronous dependency in the add method, your code could provide a callback to the add method, which it would call when it has received the asynchronous result:
myAsyncCalc.add(2, function (added) {
a = added.value();
});
Or, when using promises (which is really nice to work with), the add method would return an object to which you can assign the same call-back:
myAsyncCalc.add(2).then(function (added) {
a = added.value();
});
Note that the callback function is not part of the currently executing code. It just is a function reference, that can be used at a later, asynchronous event for calling you back. But that will be part of a separate execution sequence, that only starts when the internal event queue has been processed and an event has been processed that triggered that execution sequence.
If this is not an acceptable solution, and you really need the former syntax to somehow take an asynchronous produced result into account, then you are without hope: it is not possible, because that really represents synchronous code execution.
Wrapping your Object
You write that you cannot modify the content of the package, but can only extend it.
One way to do that is to make use of proxies.
The idea is that you trap a reference to the add method, and return
your own adapted version of the method, which can optionally still call the original method.
See the above referenced MDN article for examples.
Calling HTTP request Synchronously
If you really want to write code like this:
a = myCalc.add(2).value();
even when the implementation of add performs an HTTP request, then you could have a look at making the HTTP request synchronously. But it should be noted that this is considered bad practice.
Code Example
Here is code that performs the addition in three ways:
unmodified (synchronous)
with an asynchronous HTPP call
with a synchronous HTTP call
For the two modified versions, a proxy pattern is used. For the asynchronous example, a call back is used using the Promise pattern.
Code:
// code in module is not modified
function Calculator(value){
return {
add: function(value2){
return {
value: function(){
return value + value2;
}
}
}
}
}
// standard object creation
var myCalc = new Calculator(1); // initialized with 1
// Create a proxy for the above object, which will expose
// an asynchronous version of the "add" method. Note that the
// "myCalc" object is not modified.
var myCalcHttpAsync = new Proxy(myCalc, {
get: function(myCalc, name) {
if (name !== 'add') return myCalc[name]; // pass-through
return function(value2) {
return new Promise(function(resolve, reject) {
// Define some url
var url = 'http://api.stackexchange.com/2.2';
// Perform HTTP request
var request = new XMLHttpRequest();
// Define call back for when response becomes available
request.onload = function() {
if (request.readyState !== 4) return;
// When async task notifies it has finished:
// call the original "add" method and notify those
// waiting for the promise to get resolved
resolve(myCalc.add(value2));
};
// `true` as third argument makes the request asynchronous
request.open('GET', url, true);
request.send(null);
});
};
}
});
// Create another, alternative proxy for demonstrating
// synchronous HTTP call:
var myCalcHttpSync = new Proxy(myCalc, {
get: function(myCalc, name) {
if (name !== 'add') return myCalc[name]; // pass-through
return function(value2) {
// Define some url
var url = 'http://api.stackexchange.com/2.2';
// Perform HTTP request
var request = new XMLHttpRequest();
// `false` as third argument makes the request synchronous
request.open('GET', url, false);
// code execution "hangs" here until response arrives
request.send(null);
// process response...
var data = request.responseText;
// .. and return the value
return myCalc.add(value2);
};
}
});
// I/O
var std = document.getElementById('std');
var async = document.getElementById('async');
var sync = document.getElementById('sync');
// 1. Standard
std.textContent = myCalc.add(2).value();
// 2. Asynchronous HTTP
myCalcHttpAsync.add(2).then(function (added) {
// This needs to happen in a callback, otherwise it would be synchronous.
async.textContent = added.value();
});
// 3. Synchronous HTTP
sync.textContent = myCalcHttpSync.add(2).value();
Unmodified result: <span id="std">waiting...</span><br>
Result after asynchronous HTTP call: <span id="async">waiting...</span><br>
Result after synchronous HTTP call: <span id="sync">waiting...</span><br>
Here is my solution using promise.
Here is a link to jsbin where you can execute the code.
http://jsbin.com/qadobor/edit?html,js,console,output
function Calculator(value) {
return {
add: function(value2) {
return new Promise(function(resolve, reject) {
setTimeout(
function() {
resolve(value + value2);
}, 2000);
});
}
};
}
var myCalc = new Calculator(1); // initialized with 1
myCalc.add(2).then(function(ans){
// this callback will be called after 2 seconds after promise resolve.
console.log(ans);
});

javascript array empty outside a function

I have JavaScript code like this:
var buffer=new Array();
function fetchData(min,max){
var ajaxReq = new XMLHttpRequest();
ajaxReq.onreadystatechange = function(){
if (ajaxReq.readyState === 4) {
if (ajaxReq.status === 200) {
buffer= ajaxReq.responseText;
console.log(buffer)//this logs an array to console
} else {
console.log("Error", ajaxReq.statusText);
}
}
};
ajaxReq.open('GET', "server/controller.php?min="+min+"&max="+max, true);
ajaxReq.send();
}
fetchData(1,100);
console.log(buffer);//this log an empty array
two logs with different result, what am I doing wrong? thanks for pointers.
Ajax is asynchronous. That means that console.log(buffer) at the end is executed before the response from the Ajax request.
You should change your method to this:
function fetchData(min,max,callback){
var ajaxReq = new XMLHttpRequest();
ajaxReq.onreadystatechange = function(){
if (ajaxReq.readyState === 4) {
if (ajaxReq.status === 200) {
buffer= ajaxReq.responseText;
callback();
//console.log(buffer)//this logs an array to console
} else {
console.log("Error", ajaxReq.statusText);
}
}
};
ajaxReq.open('GET', "server/controller.php?min="+min+"&max="+max, true);
ajaxReq.send();
}
fetchData(1,100,function(){
console.log("My Ajax request has successfully returned.");
console.log(buffer);
});
You are trying to log() the buffer before the AJAX request in executed. To solve this, your fetchData function needs to handle a callback function.
var buffer=new Array();
function fetchData(min,max, callback){
var ajaxReq = new XMLHttpRequest();
ajaxReq.onreadystatechange = function(){
if (ajaxReq.readyState === 4) {
if (ajaxReq.status === 200) {
buffer= ajaxReq.responseText;
console.log(buffer)//this logs an array to console
if(typeof callback == 'function'){
callback.call(this);
}
} else {
console.log("Error", ajaxReq.statusText);
}
}
};
ajaxReq.open('GET', "server/controller.php?min="+min+"&max="+max, true);
ajaxReq.send();
}
fetchData(1,100, function(){
console.log(buffer);
});
This is the most basic implementation, and will work only if the AJAX response is successful.
This is asynchronous. So your flow goes like this:
call fetchData()
ajax request is sent, registering an onreadystatechange callback
fetchData() completes and returns
buffer is logged out, which doesn't yet contain anything.
Sometime later, the ajax request completes and triggers the callback
The callback puts things in the array.
buffer get's logged out from the callback, and you see it now has items in it.
So you are only starting the asynchronous request once you hit that first console.log. But it actually finishes long afterward.
A couple of issues here. When the ajax call completes the 2nd console.log has already executed before the variable was set.
Also,You're not using the buffer varaible as an Array.
Seems right to me. buffer is empty to start and it doesn't get set until AFTER the asynchronous call is made, so even though you're fetchingData before the second console.log, you're not receiving it until after it shows an empty buffer.
MDN: https://developer.mozilla.org/en/XMLHttpRequest
void open(in AUTF8String method, in AUTF8String url, in boolean async, in AString user, in AString password);
The third argument is used to tell the browser whether the request should be made asynchronous or not. You set it to true, thus it will be async.
Async basically means that the request is sent and meanwhile other code is executed. So, it starts the request, and while waiting for a response, it logs the buffer: before the request has finished. If you want to log the contents, do it in the onreadystatechange event or set the third argument (async) to false.

How can I make XHR.onreadystatechange return its result?

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 */
}
...

Categories