Basically just looking to see if a particular txt file exists on the server, and if so, do further processing; however, I don't think my recursion is correct, so can someone offer a few pointers - here's what I have:
function fileExists(filename) {
$.ajax({
type: 'HEAD',
url: 'http://www.example.com/system/'+filename+'.txt',
success: function() {
// Further processing if file exists
},
error: function() {
// File does not exists, run through function again-
return arguments.callee(filename);
}
});
}
It's pretty basic, there's some processing before hand that actually creates the file; however the issue is it's FTP-ed up to our domain, which means timing can vary by a few seconds, so basically I just want it to recheck until it sees that the file exists. I'll modify it a little afterwards to control the stack, possibly setting a timeout of half a second or something, but I'm not that great with javascript, so I need a few pointers to make this recursive. Any help is GREATLY appreciated.
the issue is when you try to call fileExists again via arguments.callee(fileName), the scope of the error method isn't what you think it is.
Just call fileExists.
The other you are going to have is that if your server is quick, you are going to be firing a ton of requests. You probably want to wait some time between requests. So make error contain
setTimeout(function(){
console.log('trying again....'); // this won't work in IE, I *think*
fileExists(filename);
}, 1000); // try again in a second
Finally, you should realize that the error callback only gets invoked if the server returns a 500. The 500 code usually means there was an error on your server. If a file doesn't exist, you should probably return json to indicate the file doesn't exist, and handle that case in your success callback.
error: function() {
fileExists(filename);
}
Related
I'm trying to figure out if there's any chance to receive the status of completion of a task (triggered via an ajax call), via multiple (time intervalled) ajax calls.
Basically, during the execution of something that could take long, I want to populate some variable and return it's value when asked.
Server code looks like this:
function setTask($total,$current){
$this->task['total'] = $total;
$this->task['current'] = $current;
}
function setEmptyTask(){
$this->task = [];
}
function getTaskPercentage(){
return ($this->task['current'] * 100) / $this->task['total'];
}
function actionGetTask(){
if (Yii::$app->request->isAjax) {
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return [
'percentage' => $this->getTaskPercentage(),
];
}
}
Let's say I'm in a for loop, and I know how many times I iterate over:
function actionExportAll(){
$size = sizeof($array);
$c = 0;
foreach($array as $a){
// do something that takes relatively long
$this->setTask($size,$c++);
}
}
While in the client side i have this:
function exportAll(){
var intervalId = setInterval(function(){
$.ajax({
url: '/get-task',
type: 'post',
success: function(data){
console.log(data);
}
});
},3000);
$.ajax({
url: '/export-all',
type: 'post',
success: function(data){
clearInterval(intervalId); // cancel setInterval
// ..
}
});
}
This looks like it could work, besides the fact that ajax calls done in the setInterval function are completed after "export-all" is done and goes in the success callback.
There's surely something that I'm missing in this logic.
Thanks
The problem is probably in sessions.
Let's take a look what is going on.
The request to /export-all is send by browser.
App on server calls session_start() that opens the session file and locks access to it.
The app begins the expensive operations.
In browser the set interval passes and browser send request to /get-task.
App on server tries to handle the /get-task request and calls session_start(). It is blocked and has to wait for /export-all request to finish.
The expensive operations of /export-all are finished and the response is send to browser.
The session file is unlocked and /get-task request can finally continue past session_start(). Meanwhile browser have recieved /export-all response and executes the success callback for it.
The /get-task request is finished and response is send to browser.
The browser recieves /get-task response and executes its success callback.
The best way to deal with it is avoid running the expensive tasks directly from requests executed by user's browser.
Your export-all action should only plan the task for execution. Then the task itself can be executed by some cron action or some worker in background. And the /get-task can check its progress and trigger the final actions when the task is finished.
You should take look at yiisoft/yii2-queue extension. This extension allows you to create jobs, enqueue them and run the jobs from queue by cron task or by running a daemon that will listen for tasks and execute them as they come.
Without trying to dive into your code, which I don't have time to do, I'll say that the essential process looks like this:
Your first AJAX call is "to schedule the unit of work ... somehow." The result of this call is to indicate success and to hand back some kind of nonce, or token, which uniquely identifies the request. This does not necessarily indicate that processing has begun, only that the request to start it has been accepted.
Your next calls request "progress," and provide the nonce given in step #1 as the means to refer to it. The immediate response is the status at this time.
Presumably, you also have some kind of call to retrieve (and remove) the completed request. The same nonce is once again used to refer to it. The immediate response is that the results are returned to you and the nonce is cancelled.
Obviously, you must have some client-side way to remember the nonce(s). "Sessions" are the most-common way to do that. "Local storage," in a suitably-recent web browser, can also be used.
Also note ... as an important clarification ... that the title to your post does not match what's happening: one AJAX call isn't happening "during" another AJAX call. All of the AJAX calls return immediately. But, all of them refer (by means of nonces) to a long-running unit of work that is being carried out by some other appropriate means.
(By the way, there are many existing "workflow managers" and "batch processing systems" out there, open-source on Github, Sourceforge, and other such places. Be sure that you're not re-inventing what someone else has already perfected! "Actum Ne Agas: Do Not Do A Thing Already Done." Take a few minutes to look around and see if there's something already out there that you can just steal.)
So basically I found the solution for this very problem by myself.
What you need to do is to replace the above server side's code into this:
function setTask($total,$current){
$_SESSION['task']['total'] = $total;
$_SESSION['task']['current'] = $current;
session_write_close();
}
function setEmptyTask(){
$_SESSION['task'] = [];
session_write_close();
}
function getTaskPercentage(){
return ($_SESSION['task']['current'] * 100) / $_SESSION['task']['total'];
}
function actionGetTask(){
if (Yii::$app->request->isAjax) {
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return [
'percentage' => $this->getTaskPercentage(),
];
}
}
This works, but I'm not completely sure if is a good practice.
From what I can tell, it seems like it frees access to the $_SESSION variable and makes it readable by another session (ence my actionGetTask()) during the execution of the actionExportAll() session.
Maybe somebody could integrate this answer and tell more about it.
Thanks for the answers, I will certainly dig more in those approaches and maybe try to make this same task in a better, more elegant and logic way.
$http.post('#Url.Action("donePressed")',
{
id: $scope.id,
}).then(
function (response) // success callback
{
window.location = '#Url.Action("PdfCreator", "someController")?Id=' + $scope.id;
window.location='#Url.Action("Index","AnotherController")';
},
function (response) // failure callback
{
alert(response.statusText);
});
Hi, I guess I am doing somehting wrong, I want to call to a function the sends me a file as a response, and afterwords I want to leave the page and go somewhere else.
the problem is, because this is a sync I don't get my download.
How can I make this synced?
Async has nothing to do with it. Once you're inside the success callback, the async part is already done. The problem is that you're changing the window location again before the first change has had time to load. In other words, it's the exact opposite of an async problem; the problem is that this code is synchronous and runs too fast.
However, the approach here is flawed to begin with. It might work if the browser was forced to download the file, as the then the first change to window.location would not itself cause the browser view to change. Since PDF is typically a browser-viewable type, this is not guaranteed, though. Regardless, you still have the same issue of need to delay the second call until the first has gotten a response, which is basically impossible. There's no built in event for this type of thing, so the best you'd could do would be to is use setTimeout with a 1-2 second delay, and just hope that that is enough time to get the first response. Even then, if it ever took longer, your code would break again. In other words, it's going to be extremely brittle.
The simple fact is that this is just simply not how HTTP works. You're basically trying to return two responses for a single request, which is not possible. This is a clever way to try to skirt around inherent restrictions in the protocol, I'll give you that, but it's ultimately still insufficient.
All that said, you can actually make this happen via the HTML5 File API and AJAX, but your solution then will only be compatible with modern browsers (basically everything except IE 10 and under). If you do not need to support lesser versions of IE, then you can use the following code instead:
function (response) // success callback
{
$http.get('#Url.Action("PdfCreator", "someController")?Id=' + $scope.id').then(
function (response) // success callback
{
var a = document.createElement('a');
var url = window.URL.createObjectURL(response.data);
a.href = url;
a.download = 'myfile.pdf';
a.click();
window.URL.revokeObjectURL(url);
window.location = '#Url.Action("Index","AnotherController")';
},
function (response) // failure callback
{
alert(response.statusText);
}
);
},
The secret sauce is in fetching the PDF via AJAX and then creating an object URL out of the PDF data. You can then use that to create an anchor element in the DOM and "click" it dynamically to prompt the download. The caveat, though, is that I haven't tried to do this with Angular, so I'm unsure if $http supports getting a binary response. I know with jQuery, you just have to tell it that the XHR object's response type is 'blob', but I'm not sure if you can or how you would do the same thing with Angular. As an alternative, you can simply use XMLHttpRequest directly for this particular AJAX, and simply set xhr.responseType = 'blob'.
When communicating with a server in javascript in my single page browser application, I would like to provide a callback function that is always called after the server replies, regardless of whether the result was a success or some kind of error.
Two cases where I need this:
1) I want to disable a "save" button while waiting for the server's response, and enable it again after the server responds with an error or a success.
2) I have a polling mechanism where I want to prevent stacking of calls when the server for some reason is being slow to respond - I want to wait for one poll call to finish before making the next.
One solution I have right now involves making sure that two functions (success and error) get passed along as options in a long method chain, which feels like a fragile and cumbersome solution (pseudo-ish code):
function doCall() {
framework1.callit({success : myCallback, error : myCallback})
};
framework123.callit = function(options) {
options = options || {};
if (options.error) {
var oldError = options.error;
options.error = function(errorStuff) {
// callit error stuff
oldError(errorStuff);
} else {
// callit error stuff
}
generalCallFunction(options);
}
function generalCallFunction(options) {
options = // ... checking success and error once again to get general success and error stuff in there, plus adding more options
ajax( blah, blah, options);
}
I also have a backbone solution where I listen to the sync event plus an error callback, in similar ways as above.
I'm always scared that error or success functions get lost on the way, and the whole thing is hard to follow.
Any framework or pattern for making this stuff as easy as possible? Is it a weird thing to have general things that should always happen whether the result was an error or a success?
You can use jQuery.ajax({ details here... ).always(callback);
Or, in Backbone
// logic to create model here
model.fetch().always(callback);
I am trying to write a JavaScript interface for an Api, but I can not figure out this issue. I have code to call an ajax request:
mooshark.request('userInfoFromID', {
userID : '20991'
});
That code creates an Ajax request. When it starts, it sets an internal variable to true (to indicate that it is running). Then on the next line I have this:
var data = mooshark.response();
alert(data);
The response function is as follows:
response: function () {
if(this.running == false){
return "done";
} else if (this.running == true){
alert("Running");
setTimeout(this.response, 3000);
}
}
It outputs (in this order) Running. undefined. (JSON response). Running. Not once does it output "done". Is there a way to return "done" when this.running becomes true? I would like to mention that this.running will not always be the same request time. I know there is always the option of wrapping all my code inside the onCompleat function in the ajax request, but I want to have that as a last resort.
Thanks!
This is not possible without freezing the browser.
Whenever your code is running, the browser UI will be completely frozen.
If you want the call to wait for the server to reply, the browser will need to be competely frozen. (which is not a good idea)
Since most of the time you're calling response() through setTimeout(), a return value isn't really useful.
But -- you're likely never setting your 'running' variable to false. You might post more code here (the AJAX response handling code, for example). Also: what exactly are you trying to accomplish by returning / alerting "running" and "done"?
I want to retrieve the height and width of an image on a server by using an ajax post call to a php file which returns a double pipe delimited string 'width||height'
My javascript is correctly alerting that requested string from the php file so the info is now in my script but i cannot seem to access it outside the $.post function.
This works:
var getImagesize = function(sFilename)
{
$.post("getImagesize.php", { filename: sFilename, time: "2pm" },
function(data){
alert(data.split('||'));
});
}
But retrieving is a different matter:
// this line calls the function in a loop through images:
var aOrgdimensions = getImagesize($(this, x).attr('src')) ;
alert(aOrgdimension);
// the called function now looks like this:
var getImagesize = function(sFilename)
{
var aImagedims = new Array();
$.post("getImagesize.php", { filename: sFilename },
function(data){
aImagedims = data.split('||');
});
return "here it is" + aImagedims ;
}
Anyone able to tell me what i'm doing wrong?
You are misunderstanding the way that an AJAX call works. The first "A" in AJAX stands for asynchronous, which means that a request is made independent of the code thread you are running. That is the reason that callbacks are so big when it comes to AJAX, as you don't know when something is done until it is done. Your code, in the meantime, happily continues on.
In your code, you are trying to assign a variable, aOrgdimensions a value that you will not know until the request is done. There are two solutions to this:
Modify your logic to reconcile the concept of callbacks and perform your actions once the request is done with.
Less preferably, make your request synchronous. This means the code and page will "hang" at the point of the request and only proceed once it is over. This is done by adding async: false to the jQuery options.
Thanx for the Asynchronous explaination. I did not realize that, but at least now i know why my vars aren't available.
Edit: Figured it out. Used the callback function as suggested, and all is well. :D