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!
Related
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.
I have a application where there are numerous number of ajax calls to the server.
Now I want to audit the response that comes from the server (This requirement poped up after the ajax code was laid).
So I have a function that would audit the response data, only problem is how can I get the data to be sent to the function which now sits separately.
I don't want to do the laborious work of adding the line of code for calling the function in each ajax call.
Is there easier and general way out. Somehow I could detect when a response come back and then process the response.
Using both traditional javascript method as well as jquery ajax calls in the system. (The app has been getting changes from a long time and has changed hands a lot so the new things get added and the older ones never get removed)
Wrap your ajax calls with a helper function and use it throughout your code.
An (untested) example:
MyApp = MyApp || {
logRequest: function _logRequest(settings, response) {
// Log your response
},
ajax: function _ajax (settings) {
var that = this;
// Log attempt request here?
// Example of logging the success callback (do similar for error or complete)
if (settings.success) {
// A success handler is already specified
settings.success = function (data) {
that.logRequest(settings, data); // Log the response
settings.success(data); // Call the original complete handler
};
} else {
// No success handler is specified
settings.success = function (data) {
that.logRequest(settings, data);
};
}
return jQuery.ajax(settings);
}
};
I favour this mechanism for lots situations where I want to reduce boilerplate. I only have to modify the state of the MyApp object which is my own (named appropriately for the application), so it is sort of an interface that allows you to intercept function calls without modifying other global objects. You can also swap this functionality out with something else very easily without having to update your references everywhere, which could be useful in a lot of other situations as well.
Using .ajaxComplete() should be enough to catch the onComplete event for all AJAX requests made through jQuery. Isn´t that what you´re asking for?
$('.ajaxRequest').click(function(event) {
event.preventDefault();
$.getJSON(
'/echo/json/',
this.id,
function(data, textStatus, jqXHR) {
console.log(data, textStatus, jqXHR);
}
);
});
// Listen to all ajax requests
$("#log").ajaxComplete(function(event, request, settings) {
console.log(event, request, settings);
});
View demo.
Within a backbone app, I am making a call with fetch when a users takes a certain action:
changeDay: function() {
this.collection.fetch({
success: function() {
lr.primaryView.addAllEvents();
}
});
},
...
Sometimes, a user takes actions that calls this changeDay method again before the first request has successfully responded. In these cases, I want to cancel the previous request. I am familiar with how to do this with vanilla jQuery (it is outlined here) but I am unable to easily use that approach here since the XHR object is hidden behind fetch. How can I solve this?
Backbone.fetch actually returns the jQuery XHR object:
changeDay: function() {
thisXHR = this.collection.fetch({
success: function() {
lr.primaryView.addAllEvents();
}
});
},
...
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.