Using delegates in Javascript (example using XMLHttpRequest AJAX request) - javascript

I've got an AJAX page that returns some data from a GET. All good. I'm trying to call it from Javascript in response to a user changing the value of a select element called ProductCode. I've come from the C# world if some of my terminology is a little off.
Javascript:
function ProductCodeChange()
{
try
{
elProductCode = document.getElementById("ProductCode");
ProductCode = elProductCode.value;
var AJAX_MAGICTOKEN = "773d8626-6b78-4055-bf8b-4fbbaa725550";
url = "AJAX_GetData.php?";
url += "MagicToken=" + AJAX_MAGICTOKEN + "&";
url += "Query=GetProduct&";
url += "ProductCode=" + ProductCode;
httpxml = new XMLHttpRequest();
httpxml.onreadystatechange = PopulateProductCode(httpxml);
alert(">httpxml.open");
httpxml.open("GET", url, true);
httpxml.send(null);
}
catch (e)
{
alert(e.message);
}
}
function PopulateProductCode(httpxml)
{
try
{
alert(">ProductCodePopulate");
if (httpxml.readyState == 4 && httpxml.status == 200)
{
data = httpxml.responseText;
alert(data);
}
else
{
alert("readyState: " + httpxml.readyState.toString());
alert("status: " + httpxml.status.toString());
}
}
catch (e)
{
alert(e.message);
}
}
HTML:
<select id="ProductCode" name="ProductCode" onchange="ProductCodeChange();">
<option value="AP1A" >Annual Licence</option>
<option value="AP1M" >Monthly Licence</option>
</select>
I've configured the XMLHttpRequest object to call PopulateProductCode when the onreadystatechange event is fired. I've got quite a lot of work to do with the data returned, and would prefer to have a separate function called when the XMLHttpRequest returns data.
If my C# world, the line httpxml.onreadystatechange = PopulateProductCode(httpxml); sets up the httpxml object with a delegate to call once its ready state has changed. However, when executing the code above, the order of events is:
alert(">ProductCodePopulate");
alert(">httpxml.open");
so my delegate is being called before the GET request is sent.
Am I completely misunderstanding how this object works? How can I achieve what I'd like to do (i.e. keep the functionality in a separate function)?
If you could stick to vanilla Javascript in your answer, that'd be great. I'm not familiar with JQuery yet. Thanks.
Edit:
An alternative is:
httpxml.onreadystatechange =
function()
{
if (this.readyState == 4 && this.status == 200)
{
data = httpxml.responseText;
alert(data);
}
}
This does work correctly and doesn't get executed until data is returned. From #James answer below, the first version gets executed immediately, but the version immediately above doesn't. I don't understand what is the difference between the two, except that the second doesn't have a function name. Surely they are the same thing, but one version is nameless? Why the difference in processing?

In Javascript, this line httpxml.onreadystatechange = PopulateProductCode(httpxml) first calls PopulateProductCode with the httpxml parameter, and assigns the return value of that as the event handler to use when ready state changes.
Simplest is to wrap your call to PopulateProductCode in an anonymous function.
httpxml.onreadystatechange = () => PopulateProductCode(httpxml);

I would recommend changing the approach to a newer implementation. The fetch API. It returns a promise. Improves code readability and is a better approach now days.
Suggested reading:
Fetch API vs XMLHttpRequest
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

Related

How to track http requests/responses using JavaScript? [duplicate]

I'm trying to intercept all AJAX calls in order to check if that AJAX response contains specific error code that I send as JSON from my PHP script (codes: ACCESS_DENIED, SYSTEM_ERROR, NOT_FOUND).
I know one can do something like this:
$('.log').ajaxSuccess(function(e, xhr, settings) {
});
But - does this work only if "ajaxSuccess" event bubble up to .log div? Am I correct? Can I achieve what I want by binding "ajaxSuccess" event to document?
$(document).ajaxSuccess(function(e, xhr, settings) {
});
I can do this in either jQuery or raw JavaScript.
If you're using jQuery, $.ajaxSuccess is a good option, but here's a generic option that will intercept XHR calls from all frameworks (I've tested it with ExtJS and jQuery - it should work even if multiple frameworks are loaded concurrently). It's been tested to work with IE8, Chrome and Firefox.
(function(XHR) {
"use strict";
var open = XHR.prototype.open;
var send = XHR.prototype.send;
XHR.prototype.open = function(method, url, async, user, pass) {
this._url = url;
open.call(this, method, url, async, user, pass);
};
XHR.prototype.send = function(data) {
var self = this;
var oldOnReadyStateChange;
var url = this._url;
function onReadyStateChange() {
if(self.readyState == 4 /* complete */) {
/* This is where you can put code that you want to execute post-complete*/
/* URL is kept in this._url */
}
if(oldOnReadyStateChange) {
oldOnReadyStateChange();
}
}
/* Set xhr.noIntercept to true to disable the interceptor for a particular call */
if(!this.noIntercept) {
if(this.addEventListener) {
this.addEventListener("readystatechange", onReadyStateChange, false);
} else {
oldOnReadyStateChange = this.onreadystatechange;
this.onreadystatechange = onReadyStateChange;
}
}
send.call(this, data);
}
})(XMLHttpRequest);
I've posted a more specific example on github which intercepts AJAX calls and posts the AJAX call durations back to the server for statistical analysis.
From http://api.jquery.com/ajaxSuccess/ :
Whenever an Ajax request completes successfully, jQuery triggers the ajaxSuccess event. Any and all handlers that have been registered with the .ajaxSuccess() method are executed at this time.
So the selector doesn't define the position where you are "catching" the event (because, honestly, ajax event by its nature doesn't start from a DOM element), but rather defines a scope to which the handling will be defaulted (i.e. this will poitn to that/those element(s)).
In summary - it should be exactly what you wish for
The best way, which I found https://lowrey.me/intercept-2/
const intercept = (urlmatch, callback) => {
let send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
this.addEventListener('readystatechange', function() {
if (this.responseURL.includes(urlmatch) && this.readyState === 4) {
callback(this);
}
}, false);
send.apply(this, arguments);
};
};
Try using Mockjax.js http://code.appendto.com/plugins/jquery-mockjax
It lets you hijack AJAX calls to the server and mock the location.

JavaScript static function in callback

Hi I've been trying to clarify this but there's something I'm still confused about. I know that you can't return values from asynchronous functions so I've referenced this answer's top answer Returning value from asynchronous JavaScript method?
What I'm trying to do is use the flickrAPI to get the biggest size image. The flickrAPI allows one to search images, so I use this to get the photo_id, then I use this photo_id to procses another request to the API's getSize method to get the URL for the biggest size photo.
The code looks a little messy as it is, because I have a method called flickrRequest which sends an XMLHttp request and gets back a JSON string. I know that I can achieve what I want by writing the functions as follows:
function flickRQforimage() {
...got ID
function flickrRQforSize() {
...got maxsizeURL
create image based on maxsizeURL here
}
}
but I was wondering if it was possible to do something like this
function flickRQforimage() {
...got ID
function flickrRQforSize() {
...got maxsizeURL
}
create image based on maxsizeURL here
}
or even create image based on maxsizeURL here
In general my question is whether it is possible to have a callback function that references another statically defined function (I think?). The specifics of the my function is that it takes a callback and the ID and URL processing happens in those callbacks:
flickrRQ(options, cb)
I am wondering whether/what would happen if that unnamed function is instead something else, say flickrRQ(options, processPhoto(data)), and then I define the function in a separate method. This just makes sense for me because I want to keep functionality for the URL processing separate in an attempt to make my code cleaner and more readable.
I tried the following below and it didn't work. Nothing prints. I even have a console.log in the processPhoto method. In fact anything inside of the flickrRQforSize method seems to not evaluate
flickrRQforSize(options, function(data) {
processPhoto(data)
}
even though in the flickrRQforSize definition, a callback function is taken as an argument. I'm suspecting there must be something about functions/async calls that I don't understand.
I hope this is clear -- if not, I can post my actual code.
Here's my code:
var flickrRequest = function(options, xhrRQ, cb) {
var url, xhr, item, first;
url = "https://api.flickr.com/services/rest/";
first = true;
for (item in options) {
if (options.hasOwnProperty(item)) {
url += (first ? "?" : "&") + item + "=" + options[item];
//parses to search equest;
first = false;
}
}
//XMLHttpRQ to flickr
if(xhrRQ == 1 ) {
xhr = new XMLHttpRequest();
xhr.onload = function() { cb(this.response); };
xhr.open('get', url, true);
xhr.send();
};
}
var processPhotoSize = function(photoJSON) {
var parsedJSON = JSON.parse(data);
var last = parsedJSON.sizes.size.length;
console.log(parsedJSON.sizes.size[last-1].source);
return parsedJSON.sizes.size[last-1].source;
}
...
flickrRequest(options, 1, function(data) {
...
flickrRequest(sizesOptions, 0, function(data) {
parsedJSON = JSON.parse(data);
console.log(parsedJSON);
processPhotoSize(data);
});
}

JavaScript global variable value does not change until after alerted

This is my first question and I hope I don't do anything wrong. First of all, thank you for reading.
And my problem is...
The design is to read some data in a text file with JavaScript, process them through a number of functions before creating the content to display in an HTML div.
After some searching, I figured that it could be done with XMLHttpRequest. Because the read data will be processed by some functions, I decided to store them to a global variable for easy access. The code seemed to be working fine at first and I could print the obtained data to a div. But then I noticed a strange bug. If I assign those data to a global variable and attempt to retrieve them later, I will get the initially assigned value or undefined. I try to alert that global variable's value and I see what I get above. However, if I alert again, the value changes to what I needed. I have just been learning JavaScipt for a short while, facing this error completely leaves me at lost.
The html file:
<html>
<head>
<meta charset="UTF-8">
<title>Read file</title>
<script>
var output = ["next"];
function edit()
{
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "test.txt", true);
rawFile.responseType = "text";
rawFile.onreadystatechange = function ()
{
if(rawFile.readyState === 4)
{
if(rawFile.status === 200 || rawFile.status == 0)
{
output[0] = rawFile.responseText;
//alert("Reading okay!");
}
}
};
rawFile.send(null);
console.log(output[0]); // initial value
alert(output[0]); // initial value
console.log(output[0]); // desired value
alert(output[0]); // desired value
}
</script>
</head>
<body>
<button onclick="edit()">Read test.txt</button>
</body>
</html>
The text file:
This is the content of the text file.
Temporarily, I have to alert every single time the text file is read which isn't a good idea to solve the problem.
My question is, with the above design, is there any better way to implement it without having to deal with this bug?
And here is the demo: html and text.
Thank you very much.
That's because the value changes asynchronously.
The alert is no guaranty, it's just a delay after which the AJAX callback could have been executed or not.
If you want to use the desired value, you must run your code in onreadystatechange.
Example:
function edit(callback)
{
/* ... */
rawFile.onreadystatechange = function () {
if(rawFile.readyState === 4 && (rawFile.status === 200 || rawFile.status == 0)) {
output[0] = rawFile.responseText;
//alert("Reading okay!");
callback();
}
};
/* ... */
}
fuunction afterEdit(){
alert(output[0]); // desired value
}
<button onclick="edit(afterEdit)">Read test.txt</button>
Since the AJAX call is asynchronous, it is being executed after your edit function returns... Since it sounds like you are passing your data through a series of functions, I suggest using a promise library (Q.js for instance). Here is a simple jsfiddle that demonstrates using Q.js.
Your AJAX call would simply resolve the promise, kicking off the chain of functions to execute. My example shows modifying the data at each step, but this is not necessary. The return value of the prior function will be used as the input for the next function. I've commented out the AJAX stuff and used setTimeout to mimic async call:
//Global variable for test.txt
var test;
function edit()
{
/*
var deferred = Q.defer();
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "test.txt", true);
rawFile.responseType = "text";
rawFile.onreadystatechange = function ()
{
if(rawFile.readyState === 4)
{
if(rawFile.status === 200 || rawFile.status == 0)
{
//resolve promise with responseText;
deferred.resolve(rawFile.responseText);
}
}
};
deferred.promise
.then(processStep1)
.then(processStep2)
.then(processStep3);
*/
//Imitating async call that will finish after 2 seconds
var deferred;
var promise;
//if we haven't read the file yet, then make async call
if (test === undefined) {
deferred = Q.defer();
setTimeout(function () {
test = "This is the content of the text file."
deferred.resolve(test);
}, 2000);
promise = deferred.promise;
}
//Else we've already read the file.
else {
promise = Q(test);
}
//Start adding your functions to process text here:
promise.then(processStep1)
.then(processStep2)
.then(processStep3);
}
function processStep1(data) {
alert("Step 1: " + data);
//adding some stuff onto data for example
data = data + "... And more data.";
return data;
}
function processStep2(data) {
alert("Step 2: " + data);
data = "Adding data to front. " + data;
return data;
}
function processStep3(data) {
alert("Step 3: " + data);
return data;
}
Above, I also use a global variable (test) for the data retrieved from async call. I check this value when deciding if I need to make an async call to get the value, or use the value that was already populated from the original async call. Use whatever pattern most fits your needs.
I would also recommend a library for doing the async calls as your project might get messy fast by doing raw AJAX calls.

Monkey patch XMLHTTPRequest.onreadystatechange

How would go about monkey patching the XMLHTTPRequest's onreadystatechange function. I'm trying to add a function that would be called when every ajax request made from a page come back.
I know this sounds like a terrible idea, but the use case is quite peculiar. I want to use a certain SDK with a console (jqconsole) but show status and results from ajax calls within the console without modifying the external SDK.
I've looked at this post which had great info, but nothing on monkey patching the callback which seem to exceed my JavaScript skills.
P.S Can't use jQuery since it only supports ajax calls made from jQuery not from XMLHTTPRequests directly which is the case here.
To monkey-patch XMLHttpRequests, you need to know how an AJAX request is generally constructed:
Constructor invocation
Preparation the request (setRequestHeader(), open())
Sending the request (.send).
General-purpose patch
(function(xhr) {
function banana(xhrInstance) { // Example
console.log('Monkey RS: ' + xhrInstance.readyState);
}
// Capture request before any network activity occurs:
var send = xhr.send;
xhr.send = function(data) {
var rsc = this.onreadystatechange;
if (rsc) {
// "onreadystatechange" exists. Monkey-patch it
this.onreadystatechange = function() {
banana(this);
return rsc.apply(this, arguments);
};
}
return send.apply(this, arguments);
};
})(XMLHttpRequest.prototype);
The previous assumed that onreadystatechange was assigned to the onreadystatechange handler. For simplicity, I didn't include the code for other events, such as onload. Also, I did not account for events added using addEventListener.
The previous patch runs for all requests. But what if you want to limit the patch to a specific request only? A request with a certain URL or async flag and a specific request body?
Conditional monkey-patch
Example: Intercepting all POST requests whose request body contains "TEST"
(function(xhr) {
function banana(xhrInstance) { // Example
console.log('Monkey RS: ' + xhrInstance.readyState);
}
//
var open = xhr.open;
xhr.open = function(method, url, async) {
// Test if method is POST
if (/^POST$/i.test(method)) {
var send = this.send;
this.send = function(data) {
// Test if request body contains "TEST"
if (typeof data === 'string' && data.indexOf('TEST') >= 0) {
var rsc = this.onreadystatechange;
if (rsc) {
// Apply monkey-patch
this.onreadystatechange = function() {
banana(this);
return rsc.apply(this, arguments);
};
}
}
return send.apply(this, arguments);
};
}
return open.apply(this, arguments);
};
})(XMLHttpRequest.prototype);
The main techniques used is the transparent rewrite using...
var original = xhr.method;
xhr.method = function(){
/*...*/;
return original.apply(this, arguments);
};
My examples are very basic, and can be extended to meet your exact wishes. That's up to you, however.
Assuming you can ignore IE...
//Totally untested code, typed at the SO <textarea>... but the concept *should* work, let me know if it doesn't.
var OldXMLRequest = XMLHttpRequest;
// Create a new instance
function XMLHttpRequest() {
var ajax = new OldXMLRequest();
// save old function
var f = ajax.onreadystatechange;
ajax.onreadystatechange = function() {
console.log("Whatever!");
f(); // Call the old function
}
return ajax;
}
you can learn from Ajax-hook written by chinese!
it is a advanced js to enable Monkey patch XMLHTTPRequest

Change function on javascript prototype

I want to change the XMLHttpRequest send function so that a function is called before the request is made and after the request is complete. Here is what I have so far:
var oldSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
//this never gets called
oldOnReady = this.onreadystatechange;
this.onreadystatechange = function() {
oldOnReady();
ajaxStopped();
}
ajaxStarted();
// according to http://www.w3.org/TR/XMLHttpRequest/
// there's only ever 0 or 1 parameters passed into this method
if(arguments && arguments.length > 0) {
oldSend(arguments[0]); //gets to here, calls this method, then nothing happens
} else {
oldSend();
}
}
function ajaxStarted() {
ajaxCount++;
document.getElementById("buttonClicky").innerHTML = "Count: " + ajaxCount;
}
function ajaxStopped() {
$("#isRunning")[0].innerHTML = "stopped";
ajaxCount--;
document.getElementById("buttonClicky").innerHTML = "Count: " + ajaxCount;
}
However, I'm doing something wrong here because once it hits the oldSend() call, it never returns or triggers the onreadystatechange event. So I must be doing somethingclickCount wrong here. Any ideas? I set a breakpoint and it gets hit just fine when I call this:
$.ajax({
type: "GET",
url: "file.txt",
success: function(result) {
//this never gets called
document.getElementById("myDiv").innerHTML = result;
}
});
So my new function is getting called. I guess just don't know how to call the old function. Any ideas on how to fix this code so that the Ajax Request is actually made and my new callback gets called?
Note: I'm aware of the JQuery events that essentially do this. But I'm doing this so I can get it to work with any Ajax call (Mootools, GWT, etc). I am just happening to test it with Jquery.
You need to call old functions in the context of this.
E.g.: oldSend.call(this)

Categories