Wait until unknown number of ajax request done - javascript

I'm uploading a list of documents to the server, and for each document I launch an ajax request, but the number of requests is unknown (Depends on the number of document being uploaded). How can I show a message to the user when all the documents are uploaded (All ajax requests are done).
$.each(files,function(idx,elm){
let formData = new FormData();
let ID = '_' + Math.random().toString(36).substr(2, 9);
formData.append('document_id', ID);
formData.append('file-doc', elm);
$.ajax({
url: "http://127.0.0.1:5000/add_multiple_docs",
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function (xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
},
success: function (data) {
alert(data);
},
failure: function (request) {
console.log(request);
},
error: function (jqXHR, exception) {
console.log("error add document");
let msg = '';
if (jqXHR.status === 0) {
msg = 'Not connect.\n Verify Network.';
} else if (jqXHR.status === 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status === 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
console.log(msg)
}
});
});
}

First of all, you know how many files are uploaded, so you know how many ajax request you are doing. (1 Request per file)
So before your $.each() fires you get the size of files
let count = $(files).size();
$.each(files,function(idx,elm){
/*your code with requests*/
}
Now, after each ajax request hast fired, decrement count. Decrement inside your success and failure methods, because it doesn't mattet if it succeeded or not. And check if count === 0. If it's 0 than you know all ajax are done.
$.ajax({
/*your other settings*/
success: function (data) {
alert(data);
count--;
doSomething(count);
},
failure: function (request) {
console.log(request);
count--;
doSomething(count);
},
});
function doSomething(count){
if(count === 0){
/*stuff you wannna do after all ajax requests are done*/
}
}
I haven't done that many ajax for now, so I'm not quite sure if failure is also fired on error, but if not maybe add count-- and the if on error as well.

To achieve what you need you can place all the jqXHR objects returned from $.ajax() in an array which you can apply() to $.when(). Then you can execute whatever logic you require after all of those promises have been resolved. Try this:
var promises = files.map(function(elm) {
// setup formData...
return $.ajax({
url: "http://127.0.0.1:5000/add_multiple_docs",
// ajax settings...
});
});
$.when.apply($, promises).done(function() {
console.log('all requests complete, do something here...');
});
However, it's definitely worth noting that sending AJAX requests in a loop is not a scalable pattern to use. It would be a much better idea to aggregate all the file and related data in a single AJAX request and handle that once on the server.

A very interesting question.
I had a similar issue when trying to get data from the youtube API which only returned a max result set of 50 items.
To solve this problem I used a recursive function that accepted a callback, on base case (in my case when there was no nextPageToken) I called the callback function.
The recursion was triggered in the success handler of the $.ajax request.
function fetchVideos(nextPageToken, callback) {
if (nextPageToken === null || nextPageToken === undefined) {
callback(null);
return;
}
$.ajax(requestURI + `&pageToken=${nextPageToken}`, {
success: (data) => {
// use data somehow?
fetchVideos(data.nextPageToken, callback);
},
error: (err) => {
callback(err);
}
})
}
fetchVideos("", (err) => {
if (err) {
console.log(err.responseJSON);
return;
}
updateUI();
})
When there was no longer a nextPageToken in the response it would trigger the if statement. I think it works kind of sync? Because I need the nextPageToken to perform the next request.
I know this is not the best case for you situation, but I answered based on the title which is how I came to this page :)

Related

How can I pass my JavaScript data up to my Controller in ASP.NET Core?

I have a script function that is getting all the data, but it's the data transfer to the Controller I'm having troubles with. I can't do the #Html.ActionLink, because essentially I need to select one of my game pieces and then click on an empty space to run the controller.
And these empty spaces are divs upon the onclick event would run the getPos function with inputs of their row and column.
In my GameBoard view:
var selected = false;
var row = -1;
var col = -1;
var gamePieceId = null;
var gamePiece = null;
function selectPiece(id, row, col){
gamePieceId = id;
selected = true;
gamePiece = document.getElementById(gamePieceId);
gamePiece.style.backgroundColor("lightblue");
}
function getPos(rowPos, colPos){
row = (50 * (rowPos - 1)) + 5;
col = (50 * (colPos - 1)) + 5;
if(selected){
gamePiece.style.top = row.toString() + "px";
gamePiece.style.left = col.toString() + "px";
selected = false;
addMoveToDB(gamePieceId, rowPos.toString(), colPos.toString());
}
}
function addMoveToDB(id, mrow, mcol)
{
$.ajax({
url: '/GamePiece/GameBoard',
type: 'POST',
async: true,
processData: false,
data: {
id: id,
mrow: mrow,
mcol: mcol},
success: function (data) {alert("success");},
error: function (jqXHR, exception) {
var msg = '';
if (jqXHR.status === 0) {
msg = 'Not connect.\n Verify Network.';
} else if (jqXHR.status == 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status == 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
$('#post').html(msg);
},
});
}
In my GamePiece Controller:
[HttpPost]
public async Task<ActionResult> GameBoard(string id, string mrow, string mcol)
{
try
{
GameVM gameVM = new GameVM();
GamePiece gamePiece = new GamePiece();
gamePiece = await (GamePieceManager.LoadById(Guid.Parse(id)));
gamePiece.Row = int.Parse(mrow);
gamePiece.Col = int.Parse(mcol);
await GamePieceManager.Update(gamePiece);
gameVM.gamePieces = await GamePieceManager.LoadByGameId(gamePiece.GameId);
return PartialView(gameVM);
}
catch (Exception)
{
throw;
}
}
With the success and error methods in the Ajax part, I realized that the data doesn't even go up to the controller and it throws out error 500
With the success and error methods in the Ajax part, I realized that
the data doesn't even go up to the controller and it throws out the
error right away.
Well, based on your shared code, its pretty obvious that your request will terminate soon after execution as you are using processData: false. If you set set processData: to false it stops jQuery/Javascript processing any of the data. As a result, your request has not submitted at all. You should either remove it or make it true.
Working Solution:
var id = "1";
var mrow = "mrow-Test";
var mcol = "mcol-Test";
$.ajax({
url: "/GamePiece/GameBoard",
type: 'POST',
async: true,
dataType: 'text',
data: {
id: id,
mrow: mrow,
mcol: mcol
},
success: function (data) {
alert("success");
alert(data);
console.log(data);
},
error: function (xhr) {
alert("false");
}
});
Note: I have simply removed processData: false property.
Output:

How to send additional parameters to success function in jquery ajax

I have the following Jquery code, I'm trying to display information in $('.cbs-List').HTML(divHTML); based on the region value. But in the success function, I can't read the value for the region, it states that
'data is undefined'
What is the correct form of passing parameters or values to the success function in this case?
$(document).ready(function() {
getSearchResultsREST('LA');
});
function getSearchResultsREST(region) {
var querySA = 'ClientSiteType:ClientPortal* contentclass:STS_Site Region=LA';
var queryDR = 'ClientSiteType:ClientPortal* contentclass:STS_Site Region=EM';
if(region == 'LA') {
var searchURL = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?queryText='" + querySA + "'";
} else {
var searchURL = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?queryText='" + queryDR + "'";
}
$.ajax({
url: searchURL,
method: "GET",
headers: {
"Accept": "application/json; odata=verbose"
},
contentType: "application/json; odata=verbose",
success: SearchResultsOnSuccess(data, region),
error: function(error) {
$('#related-content-results').html(JSON.stringify(error));
}
});
}
function SearchResultsOnSuccess(data, region) {
var results;
var divHTML = '';
if (data.d) {
results = data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;
if(results.length == 0) {
$('#related-content-results').html('There is No data for the requested query on ' + _spPageContextInfo.webAbsoluteUrl);
} else {
for (i=0; i<results.length; i++) {
var item = results[i];
var itemCell = item.Cells;
var itemResults = itemCell.results;
// Get values for item result
var _title = getValueByKey("Title", itemResults);
var _path = getValueByKey("Path", itemResults);
divHTML += '<li><a href=' + _path + '>' + _title + '</li>';
}
// Display information based on region.
$('.cbs-List').html(divHTML);
}
}
}
You have 2 problems, and they're both easy to fix.
There's no need to pass region into SearchResultsOnSuccess at all. you can already use it in there because it's defined at a higher scope.
In the object you're passing to $.ajax, you're not setting SearchResultsOnSuccess as a callback, you're calling it.
Change the lines:
success: SearchResultsOnSuccess(data, region) => success: SearchResultsOnSuccess
function SearchResultsOnSuccess(data, region) { => function SearchResultsOnSuccess(data) {
and it should work fine.
Edit:
Here's a basic example of how you need to set this up
function search(region) {
$.ajax({
url: 'example.com',
method: 'GET',
success: successCallback,
});
function successCallback(data) {
console.log(data, region);
}
}
search('LA');
You have to urlencode the value if it contains = or & or whitespace, or non-ASCII characters.
var querySA = encodeURIComponent('ClientSiteType:ClientPortal* contentclass:STS_Site Region=LA');
var queryDR = encodeURIComponent('ClientSiteType:ClientPortal* contentclass:STS_Site Region=EM');
if(region == 'LA') {
var searchURL = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?queryText=" + querySA;
} else {
var searchURL = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?queryText=" + queryDR;
}
And normally you don't have to put your values between apostrophes.
I updated the answer, I hope you will understand me better.
Your problem is NOT the parameter passing IMHO but your server response.
You should either:
turn on the developer tools and check the XHR requests on the network tab, look for the /_api/search/query... requests and examine the response
double check the server side logs/study your search service API documentation how to assemble a proper call
use your favourite REST client and play around your service: send there queries and check the responses and check that it matches with your expectation
last but not least, you can replace your ajax caller with this quick-and-great one:
$.ajax({
url: searchURL,
success: function (response) {
$('#post').html(response.responseText);
},
error: function (jqXHR, exception) {
var msg = '';
if (jqXHR.status === 0) {
msg = 'Not connect.\n Verify Network.';
} else if (jqXHR.status == 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status == 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
$('#post').html(msg);
},
});
(of course you should have a <div id="post"><div> somewhere in your page)
Your success function IMHO would get your region if gets called, but it does not, and I hope using one or more of these techniques will help you to see clear.
If you are really sure that you get what you want, you can go furher with passing your second argument, as described here

When one ajax is SUCCESS load next

I have been looking into a jQuery Ajax queue system. I have a step by step generator. It generates a pdf and then once the pdf is generated an image is created. Once these 2 processes are complete I then send an email confirmation. It must also be flexible to add additional steps.
However, I have yet to find an example that works. They all use 'COMPLETE' rather than 'success' so if I return an error via jSON then it is ignored. It moves on to the next in the queue
Any ideas?
EDIT
It's quite complex whats happening.
My plugin (copied from another plugin)
$.AjaxQueue = function() {
this.reqs = [];
this.requesting = false;
};
$.AjaxQueue.prototype = {
add: function(req) {
this.reqs.push(req);
this.next();
},
next: function() {
if (this.reqs.length == 0)
return;
if (this.requesting == true)
return;
var req = this.reqs.splice(0, 1)[0];
var complete = req.complete;
var self = this;
if (req._run)
req._run(req);
req.complete = function() {
if (complete)
complete.apply(this, arguments);
self.requesting = false;
self.next();
}
this.requesting = true;
$.ajax(req);
}
};
I have also written a function to speed my code up
function createQueue(file, inputid, step, params) {
var queue = new $.AjaxQueue();
queue.add({
url: file,
type: 'POST',
dataType: "json",
data: params,
complete : function(data, status) {
$('li#step' + step + ' .loading').remove();
// DO SOMETHING. CANT CHECK FOR ERRORS
},
success : function(data, status) {
// DOES NOT WORK
},
error: function(xhr, desc, err) {
console.log(xhr);
console.log("Details: " + desc + "\nError:" + err);
},
_run: function(req) {
//special pre-processor to alter the request just before it is finally executed in the queue
//req.url = 'changed_url'
$('li#step' + step).append('<span class="loading"></span>');
}
});
}
Step 1. I am using mpdf to generate a pdf. Now this takes a few seconds to actually build depending on theme, images used etc. So i call this:
createQueue('post_pdf.php', id, 1, { 'filename': filename + '.pdf', 'id': id, 'crop': crop } );
Step 2 - Generate some images
createQueue('ajax_image.php', id, 2, { 'filename': filename + '.pdf' } );
Step 3 - (something else like send email summary)
createQueue('mail.php', id, 3, { 'from': 'newfilename', 'to': 'emavle#pb.com', 'subject': 'This is a subject', 'body': 'Body Copy' } );
If it fails at step 1 I can see it in console but its not returned
As #charlietfl suggested, have each step in PHP on server side. After the AJAX call is done, you can have the response from the server and continue based on that. Example:
// make AJAX request to file.php and send 'data'
var request = $.ajax({
url: "file.php",
type: "POST",
data: { data }
});
// when PHP is done, receive the output and act accordingly
request.done(function( msg ) {
if (msg == "A") {
// plan A
} else if (msg == "B") {
// plan B
}
});

Retry ajax request after timeout

I'm using a prefilter to redo the ajax request 2 times max, see code below.
However the problem is that the original fail() handler of the ajax request is also called. This needs to be disabled of course.
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
// retry not set or less than 2 : retry not requested
if (!originalOptions.retryMax || !originalOptions.retryMax >= 2) {
return;
}
// no timeout was setup
if (!originalOptions.timeout > 0) {
return;
}
if (originalOptions.retryCount) {
originalOptions.retryCount++;
} else {
originalOptions.retryCount = 1;
// save the original error callback for later
if (originalOptions.error) {
originalOptions._error = originalOptions.error;
}
};
// overwrite *current request* error callback
options.error = $.noop();
// setup our own deferred object to also support promises that are only invoked
// once all of the retry attempts have been exhausted
var dfd = $.Deferred();
jqXHR.done(dfd.resolve);
// if the request fails, do something else yet still resolve
jqXHR.fail(function() {
var args = Array.prototype.slice.call(arguments);
if (originalOptions.retryCount >= originalOptions.retryMax || jqXHR.statusText !== "timeout") {
// add our _error callback to our promise object
if (originalOptions._error) {
dfd.fail(originalOptions._error);
}
dfd.rejectWith(jqXHR, args);
} else {
$.ajax(originalOptions).then(dfd.resolve, dfd.reject);
}
});
});
My request is: And i get the console.log message "we are in fail" at the same time as the request is redone for the first time. Any idea how to fix this?
$.ajax({
url: url,
crossDomain: true,
dataType: "json",
type: type,
timeout: 20000,
async: (async === undefined ? true : async),
beforeSend: beforeSend,
retryMax: (type == "POST" ? 0 : 2),
data: data
}).done(function(response, status, xhr) {
}).fail(function(xhr, textStatus, error) {
console.log("WE ARE IN FAIL");
});
easier way (sorry I only have the time to write partial code) :(
Create a recursive function that handle the ajax request and takes parameters + a counter.
var MyFuncAjax = function(params, counter){
if(counter <= 0){ return true; }
$ajax({
...
timeout: params.timeout
...
})
...fail(function(xhr...){
MyFuncAjax(params, --counter)
})
}
Then call it
MyFuncAjax({timeout: 20000, ....}, 2)
And voila :)

How to keep sending ajax requests until a response is received?

I need to keep hammering the same ajax request every 2 seconds or so until a response is received. This is because the device it is being sent to goes to sleep for 3 seconds at a time--it needs to catch the request when it's awake. Is there a simple way to do this by adding something to my code? Without using jquery...
This what I have for only sending one request:
function ePOST(url, postData, callback) {
var postReq = AjaxRequest();
postReq.onreadystatechange = function() {
if (postReq.readyState == 4) {
if (postReq.error) {
callback(1, "Request had an error.");
alert('postReq Error');
} else {
var stat;
try {
stat = postReq.status;
} catch (err) {
callback(1, "Failed to get HTTP status from server.");
return;
}
if (stat == 200 || stat == 0) {
callback(0, postReq.responseText);
} else {
callback(1, "Unexpected HTTP Status: "
+ postReq.status);
alert('Unexpected HTTP Status: '
+ postReq.status);
}
}
}
}
if (postReq.overrideMimeType){
postReq.overrideMimeType("text/xml");
}
setTimeout('',1000);
postReq.open("POST", url, true);
postReq.send(postData);
return postReq;
}
I think you should be able to do this with very little modification to that function.
var intervalId = window.setInterval(function() {
epost(<url>,<data>,function(error,response) {
if (!error) {
window.clearInterval(intervalId);
} else {
alert(response); // or handle error some other way
}
// do something with the data
});
},2000);
I'm not 100% sure that intervalId will be available to the closure but if not you can always make it like this:
var intervalId;
intervalId = window.setInterval(....
The only issue I can think of is if the ajax call takes longer then 2 seconds to return, in which case you may not clear the interval in time.

Categories