I am new to Javascript, and I am creating a chrome extension where I am trying to inject some HTMLs into the existing public website,
My current code is something like this,
if (document.readyState ==='complete'){
inject_html();
}
However, on the current website, when a button is pressed, ajax is processed and new HTML DOM is loaded for the same url, so the script doesn't run.
Is there a way to listen to whenever ajax is finished processing? What is the best way in this case, can an expert help me?
This is rather hacky, but you can override any property (and thus interpose any method call) of the XMLHttpRequest prototype, as well as the function/constructor itself.
The most convenient thing to do in your case is probably to hook the XMLHttpRequest function itself, and simply add an event listener there:
var originalXHR = XMLHttpRequest;
XMLHttpRequest = function()
{
var ret = new originalXHR();
ret.addEventListener('load', function()
{
console.log('Ajax request finished!');
});
return ret;
};
The control flow here goes like this:
Call original function
Modify return value
Return modified value
This takes advantage of the fact that if in JavaScript a constructor returns a value, that value replaces the object created with new.
In action:
// Interface code
var field = document.querySelector('input');
document.querySelector('button').addEventListener('click', function()
{
var xhr = new XMLHttpRequest();
// crossorigin.me because we need a CORS proxy for most external URLs
xhr.open("GET", 'https://crossorigin.me/' + field.value);
xhr.send(null);
});
// Hooking code
var originalXHR = XMLHttpRequest;
XMLHttpRequest = function()
{
var ret = new originalXHR();
ret.addEventListener('load', function(ev)
{
// var xhr = ev.target;
console.log('Ajax request finished!');
});
return ret;
};
<!-- Demo page: -->
URL: <input type="text" value="https://google.com"><br>
<button>Load via XHR</button>
Note that this only gives you a method to detect when an XHR has finished loading, and not control over its return value. From within the function that does console.log, you do however have access to the XHR itself, via ev.target.
You could also create hooking points to modify the loaded content before it reaches the code on the page, but that would require a couple more hooks, because listeners can be added in more than one way (.onload, .onreadystatechange, .addEventListener, etc), but it would be doable if necessary.
Related
I have got a working way to intercept the xhr call and modifying the video.js xhr response. But there is the problem.
My workaround to modify the video.js xhr response are
I modify the main xhr prototype and my add my async function to event listener of "readystatechange"
My Custom async function is fired. This custom function makes multiple promise xhr calls and join the content and replace the video.js xhr response to my custom response.
(function (proxied) {
XMLHttpRequest = function () {
//cannot use apply directly since we want a 'new' version
var wrapped = new (Function.prototype.bind.apply(proxied, arguments));
// Here we can subscribe to the xhr.send to save the xhr events to local variable, remove the events from xhr
// ---------------- Add custom onreadystatechange event so we will be able to modify the response --------------
wrapped.addEventListener("readystatechange", async function () {
console.log(this.readyState);
// When download completed
if (this.readyState === 4) {
console.log(this.onreadystatechange);
var customContent = await GetCustomContent();
// Update the value of the response object
Object.defineProperty(this, 'response', {
value: customContent,
writable: false
});
// Update the value of the response text
Object.defineProperty(this, 'responseText', {
value: customContent,
writable: false
});
// As we have successfully modify the response now we can add back the events to the xhr and trigger it here.
}
//this.responseText = "Customized response";
}, false);
// -----------------------------------------------------------------------------------------------------------
return wrapped;
};
})(XMLHttpRequest);
The Problem I am facing
As the video.js xhr call have their on function registered to the event "readystatechange". As I added my own function as well, my functions get called first. But due to it's asy nature while my function is running, the video.js xhr event function is triggered and so modifying the response have not any effect.
So what I think if it is possible that if we list the xhr request "readystatechange" events, save them to the local variables and then when my asy function promise returns then we add back the events which we stored on the local variable and then trigger it.
This way we will be able to modify the response and then trigger the video.js events.
But I did not know how to store the events on the variable, remove from the xhr and again added back to the xhr (If it is possible). Can you please tell me how I can do that?
I have above workaround in my mind to modify the response, if you have any better way then please let me know.
Thank You!
I have a web-app that polls for data periodically to 3rd party services (say Facebook, Twitter, and so on).
This poll/request is made via JSONP (to avoid cross-domain issue).
For example, a simple request would be something like this:
function jsonp_callback() {
// Do something
}
var url = 'http://some.service.com/getresult?callback=jsonp_callback';
$http.jsonp(url);
However since there can be another type of request that can be made at any given time (for example: to send or post an update), I created a wrapper to handle the callbacks.
The implementation is something like this:
// Callback handler
var myCb = (function() {
var F = function() {};
F.prototype.fn = {};
F.prototype.create = function(fn, scope) {
var self = this;
var id = new Date().getTime();
self.fn[id] = function() {
if (typeof fn === 'function') {
fn.call(scope);
}
}
return 'myCb.fn.' + id;
}
return new F();
})();
// JSONP request
var cb = myCb.create(function() {
// Do something
});
var url = 'http://some.service.com/getresult?callback=' + cb;
$http.jsonp(url);
If you notice, after some time, the myCb.fn will be bloated will callbacks that were old or have already executed.
Mu question is, do I need to create a mechanism to garbage-collect those who have been executed and no longer needed?
You don't necessarily need to remove old callbacks, if you will only make a few calls per page, but if your page is a long running one and makes calls repeatedly it could be a good idea to delete them.
The "mechanism" could be as simple as
delete self.fn[id];
after calling the function.
I'm currently writing JavaScript and confusing about callback. I've found it's not kind of built-in functions though...
I'm now reading O'Relly JavaScript 5th Edition and it shows a sample code something like below:
getText = function(url, callback) // How can I use this callback?
{
var request = new XMLHttpRequest();
request.onreadystatechange = function()
{
if (request.readyState == 4 && request.status == 200)
{
callback(request.responseText); // Another callback here
}
}
request.open('GET', url);
request.send();
}
Basically, I suppose I don't understand the general idea of callback though... Could someone write a sample code to take advantage of callback above?
Callbacks are pretty simple and nifty! Because of the nature of AJAX calls, you don't block execution of your script till your request is over (it would be synchronous then). A callback is simply a method designated to handle the response once it gets back to your method.
Since javascript methods are first class objects, you can pass them around like variables.
So in your example
getText = function(url, callback) // How can I use this callback?
{
var request = new XMLHttpRequest();
request.onreadystatechange = function()
{
if (request.readyState == 4 && request.status == 200)
{
callback(request.responseText); // Another callback here
}
};
request.open('GET', url);
request.send();
}
function mycallback(data) {
alert(data);
}
getText('somephpfile.php', mycallback); //passing mycallback as a method
If you do the above, it means you pass mycallback as a method that handles your response (callback).
EDIT
While the example here doesn't illustrate the proper benefit of a callback (you could simply put the alert in the onReadyStateChange function after all!), re usability is certainly a factor.
You have to keep in mind that the important thing here is that JS methods are first class objects. This means that you can pass them around like objects and attach them to all sorts of events. When events trigger, the methods attached to those events are called.
When you do request.onreadystatechange = function(){} you're just assigning that method to be called when the appropriate event fires.
So the cool thing here is that these methods can be reused. Say you have an error handling method that pops up an alert and populates some fields in the HTML page in the case of a 404 in the AJAX request.
If you couldn't assign callbacks or pass methods as parameters, you'd have to write the error handling code over and over again, but instead all you have to do is just assign it as a callback and all your error handling will be sorted in one go.
First of all I would suggest reading up on what a callback is. Here is a start.
The big picture
Callbacks are used extensively in asynchronous programming. When you don't want to block until a (possibly) long-running operation completes, one of the ways to approach the problem is to delegate the operation to someone who will do it on the side for you. This raises the question: how will you be able to tell when the operation is complete, and how will you get its results?
One solution would be to delegate the work to someone else and take a moment off your normal work every now and then to ask "is the work I gave you done yet?". If so, get the results in some way and off you go. Problem solved.
The problem with this approach is that it doesn't make your life much easier. You are now forced to ask every little while and you will not know that the operation is done as soon as it actually is (but only the next time you remember to ask). If you forget to ask, you will never be notified.
A better solution to this is the callback: when delegating work, provide a function along with it. The code which will actually do the work then promises to call that function as soon as the work completes. You can now forget all about that stuff and be secure in the knowledge that when the work is done, your callback will be called. No sooner, and no later.
What is the callback here?
In this specific case, callback is a function that you provide to getText as a manner of allowing it to communicate with you. You are in effect saying "do this work for me, and when you are finished, here's a function for you to call to let me know".
getText in fact chooses to use this callback only when the XMLHttpRequest (XHR) is completed, and at the same time it "lets you know" it passes you the contents of the HTTP response as well (so you can act upon that information).
Callbacks and more callbacks, oh my!
But take another moment to read the code. What is the value it stores to request.onreadystatechange? What is the purpose of request.onreadystatechange?
The answer is that request.onreadystatechange is there for you to populate with a callback. In effect, XHR gives you a way to provide it with a callback, and it promises to "call you back" whenever the state of the underlying HTTP request changes.
getText is a function that builds an abstraction on top of that: It plugs its own callback (an anonymous function -- I 'll refer to that as "inner") in there and accepts another callback from you (the parameter -- I 'll refer to it as "outer"). When the inner callback (which, remember: gets called whenever the state changes) detects that the state is "completed" (the meaning of the value 4) and the HTTP response status code is 200 (which means "OK"), it calls the outer callback to let you, the user of getText, know of the result.
I hope I 'm making sense. :)
Me personally I prefer to use Event Listener over callbacks.
Using Listeners comes handy especially when You're willing to process multiple asynchronous requests at once.
The Usage is as follows (taken from https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)
function reqListener () {
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send()
XMLHttpRequest callback function and files upload with data array
function HttpPost(url, arr, cb, form){
if (form === undefined) { var data = new FormData(); }else{ var data = new FormData(form); }
if (arr !== undefined) {
for (const index in arr) {
data.append(index, arr[index]);
}
}
var hr = new XMLHttpRequest();
hr.onreadystatechange=function(){
if (hr.readyState==4 && hr.status==200){
if( typeof cb === 'function' ){ cb(hr.responseText); }
}
}
hr.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log('xhr.upload progress: ' + done + ' / ' + total + ' = ' + (Math.floor(done/total*1000)/10) + '%');
};
hr.open("POST",url,true);
hr.send(data);
}
// HttpPost callback
function cb_list(res){
console.log(res);
var json = JSON.parse(res);
console.log(json.id + ' ' + json.list);
// loop
for (var objindex in json.list){
console.log(json.list[objindex].id);
}
}
Sample:
var data = [];
data["cmd"] = "get-cos";
var form = $('#form')[0];
HttpPost('/api-load', data, cb_list, form);
<form id="form" method="POST" enctype="multipart/form-data">
<input type="file" name="file[]" multiple accept="image/*">
</form>
Http header content
hr.setRequestHeader("Content-Type", "application/json");
// data:
var json = {"email": "hey#mail.xx", "password": "101010"}
var data = JSON.stringify(json);
hr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// data:
var data = "fname=Henry&lname=Ford";
What works in a proper "callback" fashion is to define a service that returns a promise like this!
$http.head("url2check").then(function () {
return true;
}, function () {
return false;
});
In the controller use the service:
<service>.<service method>.then(function (found)) {
if (found) {......
}
#jon is correct to call it Asynchronous!
I'm developing a javascript code to run on an embedded device using the ANT Galio browser.
Ideally, I'd like the code to make a get request to another server. After that get request is made, the page would not allow the user to submit another get request, until a response had been received from the previous get request.
For some reason sometimes I am receiving a readyState of 4 almost instantly. It is as though it is evaluating the previous XmlHttpRequest object and not the new one. What am I doing wrong?
<script type="text/javascript">
var fail= function (env, resp, stat) {
alert(resp);
};
var succ= function (env, resp) {
};
var canScan = true;
/* start scan */
function scan (name) {
if (canScan) {
//deactivate button
deactivateScanButtons();
//let server know
ajax = new XMLHttpRequest();
var scanUrl = 'http://19X.1XX.X.XX:8080/scan/' + name
ajax.open('GET', scanUrl, true);
ajax.onreadystatechange = function() {
if (ajax.readyState==4) {
//allow button to work again
activateScanButtons();
alert("ready state 4");
};
};
ajax.send();
//initiate scan
xrxScanInitiateScan(
'http://127.0.0.1',
"ftp.xst",
false,
succ,
fail);
}
}
function deactivateScanButtons () {
// canScan = false;
var indicator = document.getElementById('buttons');
indicator.style.visibility = "hidden";
}
function activateScanButtons () {
// canScan = true;
var indicator = document.getElementById('buttons');
indicator.style.visibility = "visible";
}
</script>
3 suggestions:
To avoid any caching on the client side, add a randomly generated number, or the current timestamp to the request querystring.
As Yoni said, initiate your XMLHttpRequest object with var keyword.
For each request, save the current timestamp within a global variable. In onreadystatechange, call activateScanButtons only if the global timestamp matches the corresponding timestamp of that given request. This way, only the latest request will be able to call activateScanButtons.
You define the ajax object in scan function without the var keyword before it. This means that it is a global object, not local. Afterwards, you have a closure - you refer to that variable in the onreadystate callback function.
I find it hard to track exactly what's going on there, but I agree with you, as you say in your question, that the callback is not using the ajax object the way you expect. You say it happens sometimes - does it happen when you make two requests almost simultaneously (double-click a button or otherwise trigger the two get requests very fast)?
I suggest that you use the var keyword before defining the ajax object. Even better, try using this in the callback function instead of referring to the ajax object by name. If it works, you have spared yourself of one closure.
I have a page that sends an XHR request when a form is submitted and I would like to get Chrome to break when it receives a response. It seems like the best way to accomplish this would be if Chrome has a javascript function that I can call that breaks execution but I've been unable to find anything like that so far. Is there another solution?
Edit:
I don't actually have a callback defined for the request so I can't set a breakpoint that way.
The request is being sent with this line of jquery code:
$.post(this.action, $(this).serialize(), null, "script");
where this is a form element. The null argument is where you would usually define a callback but with the "script" argument, raw javascript is returned by the server and then directly executed, so it seems the only way to break and step through the code is with the debugger; statement. This works, but when stepping through the code you can't actually see which line you are on so its a little awkward. I suspect that this is a limitation of Chrome's debugging tools.
drop down the chrome console (ctrl+shift+j) and type any of these:
Just rewrite the jquery ajax:
var prevajax = jQuery.ajax;
jQuery.ajax = function () { debugger; return prevajax.apply(jQuery, arguments); };
or if you are not using jQuery, rewrite the xhr class:
var prevxhr = XMLHttpRequest;
XMLHttpRequest = function (){debugger; prevxhr.apply(this, arguments);};
After it breaks, just press shift+f11 until you find the method which initiates the ajax request.
You can just set a breakpoint in the success callback and step through the debugger. To set a breakpoint:
Open "Developer Tools", and click the "Scripts" tab on the top.
Select the script that contains the success callback for your AJAX request.
Click the line number on the left hand side where you want the breakpoint. A blue arrow will show up indicating the breakpoint has been set.
Then make the AJAX request as you would, and the debugger will automatically stop at the breakpoint you set.
Alternatively, you can use the debugger statement to automatically invoke the debugger. So with this your success callback may look like:
success: function(data, textStatus, request) {
debugger; // pause execution and opens debugger at this point
...
}
Also checkout this nice article for debugging JavaScript.
However, the first approach is better as it doesn't require you to modify your code.
You can also go to scripts tab in developer tool, and on the right side you click on XHR Breakpoints and add new breakpoint with url filtering.
If you use jQuery, when breakpoint occurs you can use CallStack to find your function that called jQuery.ajax.
For me the solution was to use Initiator tab in the Network panel, it showed me the script that sent the request
ES6+
XMLHttpRequest = (Parent =>
class XMLHttpRequest extends Parent {
constructor() {
const onload = () => {
this.removeEventListener('load', onload);
debugger;
};
super(...arguments);
this.addEventListener('load', onload);
}
}
)(XMLHttpRequest);
ES5
XMLHttpRequest = (function (Parent) {
XMLHttpRequest.prototype = Parent;
return XMLHttpRequest;
function XMLHttpRequest() {
var self = this;
Parent.apply(this, arguments);
this.addEventListener('load', function () {
self.removeEventListener('load', onload);
debugger;
});
}
})(XMLHttpRequest);
This should let you inspect the request/response in the Network developer tool.
Note 01
ES5 syntax IS NOT to be understood as ES5+ mainly because function extends class ... isn't something that works at runtime. The class part will fail with Uncaught TypeError: Failed to construct 'XMLHttpRequest': Please use the 'new' operator. At that point, you may consider changing :
Parent.apply(this, arguments);
into
super(...arguments);
Note 02
Every solution here supposes that the library that issue the XHR request did not cache the XMLHttpRequest constructor [yet].
Note 03
The above code does not work with ES5 especially b/c of supper vs. .prototype inheritance syntax.
In order to circumvent thes issues, one may tap into the prototype. In the following code, I choosed to tap into the the xhr.send method.
ES5+
XMLHttpRequest.prototype.send = (function (send) {
return function () {
this.addEventListener('load', function onload() {
this.removeEventListener('load', onload);
debugger;
});
return send.apply(this, arguments);
};
})(XMLHttpRequest.prototype.send);
That said, the latter code didn't work for the issue that had me dig into this... Mainly because the internal Angular code registered an event listener (that fully redirects the browser) before even calling the xhr.send method. Not great, eh !?
That's it!
Since this question was asked (in 2010) chrome has added support for breakpoints on xhr (ajax, fetch). You can specify a substring for the url to set a conditional xhr breakpoint.
https://developer.chrome.com/docs/devtools/javascript/breakpoints/#xhr