After reading this thread jQuery Ajax Request inside Ajax Request
Hi everyone I need to have explanations about such a situation.
I've just been working on the code of a former member of my development team and found many parts of the code where he makes asynchronous ajax calls within other ajax calls.
My question is: can anyone explain the advantages and disadvantages of this practice and whether it is a good or bad practice?
Here is an example of code:
// first ajax (starting ajax call)
$.ajax({
url: "script1.php",
type: "POST",
data: {paramFisrtAjax: "first-ajax"},
success: function(response) {
alert(response);
}
});
script1.php
<script>
// second ajax
$.ajax({
url: "script2.php",
type: "POST",
data: {paramFirstAjax: "<?= $_POST['paramFisrtAjax'] ?>", paramSecondAjax: "second-ajax"},
success: function(response) {
alert(response);
}
});
</script>
<?php
// some operations on database server
echo "page2 operations with param: paramFirstAjax-> {$_POST['paramFirstAjax']}";
?>
script2.php
<?php
// some operations on database server
echo "page3 operations with params: firstParam -> {$_POST['paramFisrtAjax']} and secondParam-> {$_POST['paramSecondAjax']}";
?>
Something tells me it's not a good thing becouse i think the correct way is use the callback function success.
Like this: jquery nested ajax calls formatting
There is an advantage and a disadvantage here.
The Advantages are:
1) You make an async call, making the request a lot faster. You do not wait for the callback function, thus do not wait for your response which might take time to return. You do everything on the background rather then 'straight forward'.
This is understandable when you call multiple methods and you do not want the delay in waiting for the callback.
2) You are able to fetch a far greater amount data through your call while minimizing the need of the end client to wait.
This is useful when you have a big amount of data to display and you want to make it with minimal effort.
The Disadvantages:
1) Error handling is a pain. If something fails within the inner calls, it takes time to detect were the failure occurred and on which method.
When waiting for the callback, you can detect right away where the error occurred, as it will return a response of success or error,
2) if there is a mismatch on the data, it is hard to track back and see where the missing part took place, you will have to go through each request one by one to detect and use developer tools and/or fiddler as well, since those are async calls at the end.
3) it is easy to put too much effort on the client, since maintaining this kind of technique might result in calling multiple methods that will work together at the same time, thus creating overload on the client, locks on the threads or DB when working with server side code and more.
This explained, you can now decide for yourself with which type of method you would like to continue further in your code.
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.
I'm creating a web site using ASP. NET with a large client side that takes care of many events for the site. On the client side via AJAX I update, delete and add to the database (in that order!).
My question is, because the order of the tasks is very important: first- update database, second- delete from database, third- add to database:
Should I make the AJAX call synchronous? by changing "async" to false"?
or should I leave it as true by default?? which approach should I take?
U should do this by sending only one ajax call for all the operation you needed and make that ajax call async false.
In this case it would be better to instead use async: true and chain your requests so that they happen one after the other.
$.ajax({
type:'put',
url: '/model/7256185',
data: {name: 'Lucy'}
}).then(function () {
return $.ajax({
type:'delete',
url: '/model/7256186'
});
}).then(function () {
return $.post('/model', {name: 'bob'});
}).then(function (result) {
console.log("All Done!");
console.log(result);
}, function () {
console.log('An error has occurred!');
console.log(arguments);
});
This ensures that the requests happen in order, and it doesn't cause your page to appear broken during the requests (which is what happens with a synchronous request.)
It also allows you to use a loading gif if you so wish. With synchronous requests, loading gifs won't spin.
You DEFINITELY need to use transactions either at the business or at the data layer of your backend.
I usually prefer performing brief tasks (each entity with their own repositories) and keep the connection open for as little as possible in the Data Layer, then manage the transactional logic in the business layer using - for example - the TransactionScope class.
After this, it doesn't really matter whether you call the service/method in a sync or async fashion.
This question already has answers here:
Sequencing ajax requests
(10 answers)
Closed 9 years ago.
I am working a script, I need to loop an array of AJAX requests:
$('#fetchPosts').click(function(){
for(var i=0; i < link_array.length; i++) {
settings = {
// some object not relevant
}
var status = main_ajaxCall(settings, i); // ajax call
}
});
function main_ajaxCall(settings, i) {
$.ajax({
type: "POST",
url: "../model/insert.php",
data:{obj_settings: settings},
dataType: "json",
cache: false,
success: function (data) {
// some handeling here
return 0;
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
return 1;
},
};
Why does the AJAX requests fire instantly? It does not seem to wait for a response from model/insert.php, is there any way to force it to wait for a response before firing the next AJAX request?
EDIT 1:
It seems I wasnt clear, sorry, I dont want it to wait, I want to queue up the calls.
I cant make the call in one request, this is impossible in my current situation.
Set async to false if you want to wait for a response (default: true)
$.ajax({
async: false,
...
http://api.jquery.com/jQuery.ajax/
If you do not want blocking, you can set a success handler function using .ajaxComplete(), and you have to keep track of active AJAX connections if you want to wait for all to complete - How to know when all ajax calls are complete
The best solution would be to minimize the number of AJAX requests to one. If you have to make a loop of AJAX requests, the logic could be simplified somewhere (put that in the server perhaps?)
EDIT 1: (In response to OP edit)
If you want to queue the AJAX requests, this question has been answered before here:
Sequencing ajax requests
Queue ajax requests using jQuery.queue()
You could also use these libraries (all you needed to do was Google):
https://code.google.com/p/jquery-ajaxq/
http://codecanyon.net/item/ajax-queue-jquery/full_screen_preview/4903957
http://schneimi.wordpress.com/2008/03/10/multiple-ajax-requests-problems-and-ajaxqueue-as-solution/
It fires instantly and doesn't wait around because that's what AJAX does best (The first A stands for asynchronous).
The request to a server could take a long time to respond, and in most cases, don't want user's browser's freezing up or stopping them from doing anything else. If you do, you could probably just use a normal request.
This is the reason you give it functions for success error, so it can call them when the server responds.
If you want nothing to be able to happen in the browser while you're calling insert.php, you could drop an overlay (eg. dark div) over everything with a loading image and remove it on success.
Maybe replace the $('#fetchPosts') element with "loading..." text and then reverse it when done. Hiding visibility of the fetchPosts element and adding a different "loading.." element is a nice way.
Your AJAX call will wait for a response from the server, but wil do so asynchronously. That is, your script will continue to execute rather than block the browser while the server responds. When the server responds (or when the request times out - usually several seconds) your success: or error: functions will then execute.
The effect of your code here is to create several concurrent requests based on the link_array length.
You could specify async:false in your AJAX call, but this would freeze the browser while all the AJAX calls are made.
You should rewrite your code to execute all the handling as part of your success: function. I'd recommend you rewrite your code to assemble all your request into one, and make one AJAX call rather than several, and have the server return all the responses as one block. I can't suggest exactly how you do that - it's implementation dependent.
EDITED:
In response to your clarification, if you want them to be called in order, you'll need the success function to call the next one. You'll then have a chain of success calls the next, whose success calls the next, whose success calls the next.. etc until the last one which does the final processing. One way would be to pass the call number to the success function.
I'd like to preface this with an apology if I'm doing things in a "weird" way, as I'm primarily a C developer and am solving this AJAX problem the way I would in C.
I have a script that will be connecting to a "push server" that waits until a message is available, then sends only that one message and breaks the connection. The client must then reestablish the connection to listen for future messages.
I tried to do this by implementing a synchronous AJAX call within an asynchronous callback, and it works except it appears the DOM (maybe? I'm showing my ignorance of JS here) will block until all calls are complete.
I do not know how to do it with purely asynchronous calls as I do not want to end up exhausting the stack by having a callback calling a callback each time.
This is the code:
$.ajax({
url: './recoverDevice',
data: JSON.stringify(requestData),
dataType: 'json',
type: 'POST',
success: function(j)
{
console.log(j);
if (j.success)
{
//Indefinitely listen for push messages from the server
var loopMore = true;
while(loopMore)
{
$.ajax({
async: false,
url: './getPendingMessage',
dataType: 'json',
type: 'POST',
success: function(j)
{
//alert(j.message);
$("#progressBox").append("<li>" + j.message + "</li>");
loopMore = !j.complete;
}
});
}
}
else
{
$("#errorBox").show();
$("#errorBox").text(j.errorMessage);
}
}
});
Now, logically, this code should work. Within an asynchronous function, I loop over a synchronous JS call, each time I get a message I will append it to the DOM, and only when the server tells me there will be no more messages do I exit the loop, ending the asynchronous thread and completing the task.
The problem is that the DOM access appears to be all coalesced once all messages have been received. i.e. the appends only happen once all messages have been received and the asynchronous thread has exited.
The commented out alert was a test - it works perfectly. I get a message box after each and every notification, and it pauses correctly until the next message (with the rest of the code as-is).
I'm guessing this is my browser (Chrome) doing some magic to protect against race conditions by not allowing DOM manipulation until the asynchronous thread has exited? Or am I way off the mark and barking up the wrong tree here?
Getting rid of the loop and setting async to true makes the first message be received properly (no problems there), but obviously no messages thereafter.
Obviously I could do something like this:
function GetMessage()
{
$.ajax({
async: true,
url: './getPendingMessage',
dataType: 'json',
type: 'POST',
success: function(j)
{
$("#progressBox").append("<li>" + j.message + "</li>");
if (!j.complete)
{
GetMessage();
}
}
});
}
But that would result in a stack overflow over time (no?).
An obvious solution would be to use asynchronous calls here too, but to signal a while loop to pause and continue with new calls via some sort of synchronization primitives, but appears that JS does not have signalling primitives?
Figured this one out - I don't know why I didn't see this before but my latter code fragment works perfectly. I didn't realize it at the time of posting, but it can't overflow the stack because each time it runs it launches an async call and exits - so the stack frame is never more than 2 or 3 deep. The asynchronous calls are managed externally and won't be on the stack, so each time it starts over.
I'd still appreciate any input on why the first method (synchronous code in asynchronous call) didn't/wouldn't work.
I am creating fQuery API on top of FB javascript SDK. And till now everything worked fine, but i got stuck in FB.api calls now.
Actually, I am trying to load facebook user object i.e. "/me" using FB.api function.
function somefunc() {
var r = fQuery.load(selector); //selector = "me"
return r;
}
fQuery.load = function( selector ) {
fQuery.fn.response = "";
return FB.api( "/" + selector, function (response) {
// we get response here.
});
}
Is it possible to return the response or can we make it sync call. I have tried many ways to work around but could not get success.
Please provide suggestions.
If you think about it, you don't really want to make it synchronous. Javascript is single threaded by nature, making something that is asynchronous synchronous, would involve "freezing" the thread until the asynchronous call returns.
Even if you could do it, you don't want to, trust me.
Redesign your code to work with the asynchronous nature instead of fighting it. you will create better applications, have happier users and become a better coder all at the same time.
As commented elsewhere, making a synchronous call is useful if you want to open a popup after a successful response as browsers will often block popups that aren't a result of a direct user action.
You can do this by manually calling the Open Graph API with JavaScript (or jQuery as per the example below) rather than using the Facebook JS SDK.
e.g. to upload a photo via the Open Graph API and then prompt the user to add it as their profile picture using a popup, without the popup being blocked:
$.ajax({
type: 'POST',
url: 'https://graph.facebook.com/me/photos',
async: false,
data: {
access_token: '[accessToken]',//received via response.authResponse.accessToken after login
url: '[imageUrl]'
},
success: function(response) {
if (response && !response.error) {
window.open('http://www.facebook.com/photo.php?fbid=' + response.id + '&makeprofile=1');
}
}
});
You probably want to call FB.api in a for loop iteration if this is the case you need its proper solution which exists in the use of Closures. Please read my answer here, given in another question
My solution was to make a recursive call until i got what i need from the FB.api