What's the best way to loop through ajax calls - javascript

I'm working with Sharepoint and I have an app that creates lists in the site collection (not the app!). Creating the list isn't a problem.
I wan't to create 15 columns and add them to the default view. I prepared a "config" with the information for the list and its fields.
var ChangeRequestLogListConfig = {
listName: 'ChangeRequestLog',
fields: [
{ 'FieldTypeKind': 8, 'Title': 'STC Relevant', 'InternalName': 'STCrelevant', 'StaticName': 'STCrelevant' },
{ 'FieldTypeKind': 3, 'Title': 'Description/Reason', 'InternalName': 'DescriptionReason', 'StaticName': 'DescriptionReason' },
{ 'FieldTypeKind': 6, 'Title': 'Source of Change', 'InternalName': 'SourceOfChange', 'StaticName': 'SourceOfChange', 'Choices': { 'results': ['customer', 'internal'] } },
//12 more objects like the above
]
}
There we come to the problem: with the rest resources .../ViewFields/AddViewField and .../fields (with http post to create new ones) only support one parameter which means I have to do 2x15 ajax calls.
What's the proper approach to doing all these 30 operations and then do a final callback? Of course I know how to loop through the array of fields, I simply have no clue how to build that final callback the proper way.
Update / Follow-up
With the help of a few answers I managed to build the following code:
var spExecutor = new SP.RequestExecutor(_spPageContextInfo.siteAbsoluteUrl);
var requestUrl = _spPageContextInfo.siteAbsoluteUrl + "/_api/SP.AppContextSite(#target)/web/Lists/getbytitle('" + listConfig.listName + "')/fields?#target='" + hostWebUrl + "'";
//map field configs to promises
var promises = listConfig.fields.map(fieldConfig => {
return spExecutor.executeAsync({
url: requestUrl,
method: "POST",
body: JSON.stringify(fieldConfig),
headers: {
"accept": "application/json;odata=verbose",
"content-type": "application/json; odata=verbose"
},
error: err => console.log(err)
});
});
//Wait for all calls to be done
$.when.apply($, promises).then(callbackFn);
As you probably already saw, I'm not using $.ajax but the SP.RequestExecutor.
The problem: This doesn't return promises the same way as jQuery's ajax. This results in timing problems (--> 15 Calls at once, browsers mostly only support 4 parallel calls). The code above works, if I set a breakpoint and wait 1-2 seconds between the calls it creates all fields as expected.
My follow-up question: How can I wait for the completion of the first call to init the second, then the third and so on?
I'm not sure if I'm using the promises the right way.

Use .map to convert each field into a Promise:
var promises = ChangeRequestLogListConfig.fields.map(
item => $.ajax( ... )
);
and then $.when to wait for them all to complete:
$.when.apply($, promises).then( ... );
Note that this will start as many AJAX requests in parallel as your browser will permit - typically four.
EDIT since you've said you actually want the AJAX queries to run in series (to avoid server 409 errors) my version of your addFieldsToList function would look like this:
function addFieldsToList(listConfig) {
return listConfig.fields.reduce(function(promise, field) {
return promise.then(executeRequest(field, listConfig.listName));
}, Promise.resolve(null));
}
avoiding passing the callback, since the return value of this function will itself be a Promise that you can chain:
addFieldsToList(myConfig).then(callback);

Finally I ended up doing this:
//Promise returning request
function executeRequest(fieldConfig, listName) {
return function () {
return new Promise(function (resolve, reject) {
var requestUrl = _spPageContextInfo.siteAbsoluteUrl + "/_api/SP.AppContextSite(#target)/web/Lists/getbytitle('" + listName + "')/fields?#target='" + riskapp.utils.getSpHostUrl() + "'";
var spExecutor = new SP.RequestExecutor(_spPageContextInfo.siteAbsoluteUrl);
spExecutor.executeAsync({
url: requestUrl,
method: "POST",
body: JSON.stringify(fieldConfig),
headers: {
"accept": "application/json;odata=verbose",
"content-type": "application/json; odata=verbose"
},
success: resolve,
error: reject
});
});
};
}
//Add fields to list according to config
function addFieldsToList(listConfig, callback) {
//Prepare empty/resolved promise to iterate later
var promise = Promise.resolve(null);
//Loop through every field
$.each(listConfig.fields, function (i, field) {
promise = promise.then(executeRequest(listConfig[field], listConfig.listName));
});
//execute callback when all fields are created
promise.then(callback);
}
This executes all the calls in an order, not simultaneously.

Related

Trying to make an AJAX call asynchronous in Javascript

I am trying to retrieve some data from neo4j for my web app. I have my code structured in the following manner:
When I click the button to retrieve the data, call
var childNodes = getAllChildNodes(uuid, ""); //uuid: node specific id in neo4j, second param not important
//do something with childNodes
....
In getAllChildNodes(),
function getAllChildNodes(uuid, filter) {
/*
prepare json data to send
*/
var resultNodes = {}
var successFunction = function(data) {
//store data in resultNodes ...
//do something with the data ...
}
var failFunction = function(xhr, status, error) {
//if query fails
};
//call to API function
try {
getChildrenAPI(jsonData, successFunction, failFunction)
} catch (err) { console.log(err) }
return resultNodes
}
In getChildrenAPI
function getChildrenAPI(jsonData, doneFunction, failFunction) {
var request = $.ajax({
method : 'POST',
url: myurl,
data : JSON.stringify(jsonData),
dataType : 'json',
contentType : 'application/json',
cache : false,
async : true,
});
request.done(function (data) {
doneFunction(data)
})
request.fail(function (xhr, status, error) {
failFunction( xhr, status, error );
});
}
The problem is that my childNodes var does not get populated. When I inspected further, in my getAllChildNodes() function, resultNodes is returned before the query data is stored in successFunction(). I thought this would be an async issue, so I made sure to check that the AJAX call had its async property set to true, but that didn't solve it. So I tried using async await on my getAllChildNodes(), but that didn't work either. So my question is, what am I doing wrong here? I'm still new to the idea of async so this was the best I can do. If someone can please help me with this I would really appreciate it.
It seems that you misunderstood the problem. AJAX requests are asynchronous by default. What you want, as far as I can tell by seeing your code is to be able to use the result of the request after the request in the code. For that you need to make it synchronous. You can specify async to be true, you can await and so on. However, it's a terrible idea in most cases to make your requests asynchronous. If you synchronize your request, then nothing else will run and your page will hang while you await.
What if a request lasts for 10 seconds? In that case your page is unresponsive for ten seconds if you synchronize the request.
What if you send 100 requests and on average they take 1 second? Then your page hangs for 100 seconds.
The best practice is to avoid syncrhonising your requests whenever possible and only do so when absolutely necessary. Instead, you will need to get used to callbacks, that is, functions defined to be executed once the request is completed and define the post-request behavior in them. You could also use promises or web workers, depending on your exact situation.
async function getAllChildNodes(uuid, filter) {
/*
prepare json data to send
*/
var resultNodes = {}
var successFunction = function(data) {
//store data in resultNodes ...
//do something with the data ...
}
var failFunction = function(error) {
//if query fails
};
//call to API function
try {
var data = await $.ajax({
method : 'POST',
url: myurl,
data : JSON.stringify(jsonData),
dataType : 'json',
contentType : 'application/json',
cache : false,
async : true,
});
successFunction(data);
} catch (err) {
console.log(err);
failFunction(err);
}
return resultNodes
}
var childNodes = getAllChildNodes(uuid, "");
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
Javascript is single-threaded & non-blocking language so it will not execute code asynchronously.
To make your code sync, you have to create an async function that manage the async code (ajax, timeout, read a file, ...)
I think you're looking for something like the following:
getAllChildNodes(uuid, "", function done(results) {
// Results populated by the done callback.
console.log(results);
});
The trick here is that you need to be keeping track of how many requests were kicked off and when they finished.
So we can then change the definition of getAllChildNodes to call our doneCallback once all requests have been "processed".
function getAllChildNodes(uuid, filter, doneCallback) {
// How many calls do we need to make.
const callsToMake = [1,2,3];
// Track when all calls were made by the results.
const results = [];
const ajaxDoneCallbackCheck = function () {
if (results.length === items.length) {
doneCallback(results);
}
};
const ajaxSuccessCallback = function (data) {
results.push(data);
ajaxDoneCallbackCheck();
};
const ajaxFailCallback = function (error) {
results.push(error);
ajaxDoneCallbackCheck();
}
// Iterate through ajax calls to make.
for (const callToMake of callsToMake) {
// Do ajax stuff.
console.log('Request data');
getChildrenAPI(ajaxSuccessCallback, ajaxFailCallback);
}
}
Now results needs to be processed in our original done callback like so:
getAllChildNodes(uuid, "", function done(results) {
// Results populated by the done callback.
console.log(results);
// Iterate results.
for (const result of results) {
if (result instanceof Error) {
console.error(result);
} else {
// Process or track result!
console.log(result);
}
}
});

How to send Javascript/jQuery AJAX POST requests sequentially, looping over an array of request data, using Promises?

I have a huge list of articles,
visually indicated by a table listing of article titles,
on which we have to perform a particular processing action,
processing items one after the other,
by sending a POST request with the id of that article,
while removing that row from the table listing, as a visual indicator of the progress.
This is what I used originally:
var article_ids = [1,2,...];
article_ids.each(function (value, index) {
var id = value;
jQuery.ajax({
type: "POST",
url: "index.php?process_article",
data: {article_id: id},
cache: false,
async: false
})
.done(function(reponse) {
console.log(response);
jQuery('.article-id-' + id).css('display', 'none');
});
});
And it works fine in FireFox, processing items one by one, removing corresponding rows from the table listing.
But in Google Chrome, all of these seem to be processed in one go, instead of these requests happening one after the completion of the other.
After a bit of googling, found references saying to use Javascript Promises, which I couldn't figure out how to use for this case.
And then, instead, I changed the above, to the following recursive function, which seems to work fine on both Chrome and Firefox, with requests happening one after the other:
var article_ids = [1,2,...];
function processIds(ids) {
if (ids.length > 0) {
id = ids.shift();
console.log("Processing ID: " + id);
jQuery.ajax({
type: "POST",
url: "index.php?process_article",
data: {article_id: id},
cache: false
})
.done(function() {
jQuery('.article-id-' + id).css('display', 'none');
processIds(ids);
});
} else {
alert("Successfully processed all articles.");
}
}
processIds(article_ids);
DOUBTS:
Is this a good way to go about this?
How could we achieve the same using Javascript Promises?
The recursive solution you've suggested works and makes sense, IMO. In this situation I usually use Array.reduce, though.
Here's an example:
function processIds(ids) {
return ids.reduce((promise, nextId) => {
return promise.then(() =>
jQuery.ajax({
type: "POST",
url: "index.php?process_article",
data: { article_id: nextId },
cache: false,
})
);
}, Promise.resolve());
}
Note that the return values contained in the promises are thrown away in this example and only the very last value will be retained. It would be possible to modify this to capture and aggregate all return values if you desired.
It's also easy to extract this into something reusable. For example:
function forEachAsync(myArray, funcPromise) {
return myArray.reduce((promise, nextValue) => {
return promise.then(() => funcPromise(nextValue));
}, Promise.resolve());
}
And then you could use your new forEachAsync function as follows:
function processIds(ids) {
return forEachAsync(ids, processArticle);
}
function processArticle(id) {
return jQuery.ajax({
type: "POST",
url: "index.php?process_article",
data: { article_id: nextId },
cache: false,
});
}
I believe the latest versions of jQuery use promises that are compatible with JavaScript promises, but this is something you should check and be aware of.
I believe they were introduced in ES2015 and jQuery introduced compatibility in v3.0.

Two requests in one time immediatly. ASP MVC + JQuery Ajax

MVC application (ASP.NET MVC, client: jquery).
Problem: The second ajax-request wait, when the first ajax request will done.
I need, when the first and the second ajax-requests executes immediatly in one time.
The page sends to server to determine the count of records (the first ajax-request), very long (~5-7 seconds).
The operator click the buttom to open the card to edit it (the second ajax-request, fast, get the Dto-model).
The user doesn't need to wait the first request, he wants to work immediatly.
As a result, in Chrome in network page, two requests in status 'pending'. The second waits the first.
Question, how can I send requests, to execute asynchronously ?
The first ajax-request:
`window.jQuery`.ajax({
type: 'POST',
url: Url.Action("GetCountBooks", "Book");
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({ typeBook: "...", filter: "..." };),
success: function (data) {
// show in UI page the count of books by filter and params
},
error: function (data) {
//show error
}});
public class BookController : Controller
{
[HttpPost]
public NJsonResult GetCountBooks(string typeBook, Filter filter)
{
var data = DbProvider.GetCountBooks(typeBook, filter)
if (data.Result == ResultType.Success)
{
var count = data.Data;
return new NJsonResult
{
Data = new { Data = count }
};
}
return new NJsonResult
{
Data = new { Error = "Error while counting the books." }
};
}
}
The second ajax-request:
`window.jQuery`.ajax({
type: 'POST',
dataType: 'json',
contentType: "application/json",
url: Url.Action("GetBookById", "Book"),
data: JSON.stringify({ id: bookId }),
success: function (data) {
// show jquery dialog form to edit dto-model.
},
error: function (data) {
//show error
}});
public class BookController : Controller
{
[HttpPost]
public NJsonResult GetBookById(int id)
{
var data = DbProvider.GetBookById(id)
if (data.Result == ResultType.Success)
{
var book = data.Data;
return new NJsonResult
{
Data = new { Data = book }
};
return new NJsonResult
{
Data = new { Error = "The book is not found." }
};
}
return new NJsonResult
{
Data = new { Error = "Error while getting the book." }
};
}
}
I Cannot union ajax requests into one! The user can send various second request.
You need a fork-join splitter to fork 2 tasks and join based on some condition.
For example here is my implementation:
function fork(promises) {
return {
join: (callback) => {
let numOfTasks = promises.length;
let forkId = Math.ceil(Math.random() * 1000);
fork_join_map[forkId] = {
expected: numOfTasks,
current: 0
};
promises.forEach((p) => {
p.then((data) => {
fork_join_map[forkId].current++;
if (fork_join_map[forkId].expected === fork_join_map[forkId].current) {
if (callback) callback(data)
}
})
});
}
}}
Pass any number of async tasks (promises) into fork method and join when all are done. The done criteria here is managed by simple global object fork_join_map which tracks the results of your fork-join process (global is not good but its just an example). The particular fork-join is identified by forkId which is 0..1000 in this example which is not quite good again, but I hope you got the idea.
With jQuery you can create promise with $.when( $.ajax(..your ajax call) )
In the end you can join your promises like this
fork([
$.when( $.ajax(..your ajax call 1) ),
$.when( $.ajax(..your ajax call 2) )
]).join(() => {
// do your logic here when both calls are done
});
It's my own implementation, there may be already-written library functions for this in jQuery - I dont know. Hope this will give you a right direction at least.
The solution is to add attribute to Asp Controller: [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
http://johnculviner.com/asp-net-concurrent-ajax-requests-and-session-state-blocking/

Sequentially execute two functions with jQuery in for loops

I'm pretty new to Javascript. Please don't make it too harsh :)
I have two functions, both of which involve executing jQuery requests within for loops. For example,
function a(n,locations) {
for (var i = 0; i < n; i ++) {
$.ajax({
url: 'https://geocoder.cit.api.here.com/6.2/geocode.json',
type: 'GET',
dataType: 'jsonp',
jsonp: 'jsoncallback',
data: {
searchtext: input,
app_id: APP_ID,
app_code: APP_CODE,
},
success: function (data) {
handleData(data,locations);
}
});
}
The handleData() function would make changes to the empty array locations from the jQuery data. My function b(m) is of similar format but would use the updated locations as input.
Now, I have a c(n,m) in which I would like execute a() and b() sequentially:
function c(n,m) {
var locations = [];
a(n,locations);
b(m,locations);
}
From previous answers I understand that sequentially executing functions involving jQuery calls can be achieved by using promises (such as .then). However, this solution is only applicable when a(n) returns a promise, which is not achievable under the for-loop structure. Could you please share your insights on how to solve this issue? Thanks in advance for the help.
I would suggest recursion instead of your for loop. For example, you can call the function recursionExample like this,
function a(n) {
return new Promise ((resolve, reject) {
(function recursionExample(a) {
if (a === n) {
resolve;
} else {
$.ajax({ url: 'https://geocoder.cit.api.here.com/6.2/geocode.json',
type: 'GET',
dataType: 'jsonp',
jsonp: 'jsoncallback',
data: {
searchtext: input,
app_id: APP_ID,
app_code: APP_CODE,
},
success: function(data) {
handleData(data);
recursionExample(a + 1);
}
});
}
})(0);
});
}
This will then allow you to use the promise and .then functions. Like so...
function c(n,m) {
var locations = [];
a(n,locations)
.then (function() {
b(m,locations);
});
}

Turn several ajax requests into Observables with RxJS

I'm struggling with something - which I'm guessing means I've misunderstood and am doing something silly
I have an observable and need to use it to create some object, send that to the server for processing, combine a result from the server with the object I sent, and then turn that into an observable so what I want to do (I think) is something like
var theNewObservable = my.observable.things.select(function(thing) {
var dataToSend = generateMyJavascriptObjectFrom(thing);
var promise = $.ajax({
type: 'POST',
url: http://somewhere.com,
data: dataToSend
}).promise();
return rx.Observable.fromPromise(promise).subscribe(function(data, status, jqXHR) {
var infoFromServer = jqXHR.getResponseHeader('custom-header-returned');
// I'm wanting this to be the thing other code can subscribe to
return { infoFromServer: dataToSend };
}, function(err) {
alert('PC LOAD LETTER!');
console.error(err);
});
}
});
theNewObservable.subscribe(function(combinedInfo) { console.log(combinedInfo) };
where I'm expecting {infoFromServer: dataToSend} I'm getting an AutoDetachObserver and I can see that has an onNext with the ajax onSuccess signature so I'm obviously doing something silly
A couple things that should help a bit:
1) The subscribe method is a terminal method, as in, it won't return anything. It is where the Observer attaches so there should be no further data propagation after the subscribe
2) The onNext method of subscribe can only take a single value which you will need to have all the message data wrapped in.
Since jQuery's Promise will not behave well with this, you have two options. First, you can use the RX-DOM project for an Observable ajax version. Or you will need to wrap the promise method. If you further need to wait on the response you should be using selectMany instead, which will allow you to fire off the promise, then await its return and map the response to the original request.
var theNewObservable = my.observable.things
//Preprocess this so that `selectMany` will use
//dataToSend as the request object
.map(function(thing) { return generateMyJavascriptObjectFrom(thing); })
.selectMany(function(dataToSend) {
var promise = $.ajax({
type: 'POST',
url: http://somewhere.com,
data: dataToSend
}).promise();
//Rewrap this into a promise that RxJS can handle
return promise.then(function(data, status, jqXHR) {
return {data : data, status : status, jqXHR : jqXHR};
});
}, function(request, response) {
return {
infoFromServer : response.jqXHR.getResponse('custom-header'),
dataToSend : request
};
});
theNewObservable.subscribe(
function(combinedInfo) {
console.log(combinedInfo)
},
function(err) {
alert('PC LOAD LETTER!');
console.error(err);
});

Categories

Resources