How do I know when the last async operation will finish? - javascript

I'm building an app in NodeJs and it involves sending external requests asynchronously. Previously I had one id:
# client
function sendAjax(id) {
$.ajax({
type: "POST",
url: "/fsfdsfd",
data: JSON.stringify({"id": id}),
contentType: "application/json; charset=utf-8",
dataType: "json",
}).done(function (data) {
//.....
# server
app.post("/dsfdsfd", function (req, res, nxt) {
var id = req.body.id;
anotherServerClient.sendExternalRequest(id), function(data) {
//success
//return the result, but when exactly?
res.end("ok");
}, function (e) {
// error, but when exactly?
res.end("error");
});
Now I have an array:
# client
function sendAjax(ids) {
$.ajax({
type: "POST",
url: "/fsfdsfd",
data: JSON.stringify({"ids": ids}),
contentType: "application/json; charset=utf-8",
dataType: "json",
}).done(function (data) {
//.....
# server
app.post("/dsfdsfd", function (req, res, nxt) {
var ids = req.body.ids;
for (var id in ids) {
anotherServerClient.sendExternalRequest(id), function(data) {
//success
//return the result
res.end("ok");
}, function (e) {
// error
res.end("error");
});
}
}
How can I know when the last operation in the loop "for (var id in ids) {" will finish to
return the result to the client only after that? What's the idiomatic and simple solition?

// server
app.post("/dsfdsfd", function (req, res, nxt) {
var ids = req.body.ids;
// create output array to collect responses
var output = [];
for (var id in ids) {
anotherServerClient.sendExternalRequest(id, function(data) {
// on success, push the response to the output array
output.push(data);
// check if all responses have come back, and handle send
// if the length of our output is the same as the list of requests
if(output.length >= ids.length){
//return the results array
res.end("ok");
}
}, function (e) {
// if any api call fails, just send an error back immediately
res.end("error");
});
}
});

There are a couple of ways to do this, all idiomatic and down to matter of personal taste.
There is a library called async that provides help in these kind of operations. It specifically contains methods for going through a collection and doing something asynchronous with each of the items. Check out forEachOf, forEachOfSeries, forEachOfLimit for details.
You can achieve this using Promises. Make your API return Promises instead of accepting callbacks. Then create an array of Promises, one for each call and then then wait for all them with Promise.all.
ES6 specification now includes Promises so that is an indication what will be the favorite way in the future.

Related

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);
});
}

What's the best way to loop through ajax calls

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.

Using custom function as parameter for another

I'm currently dealing with refactoring my code, and trying to automate AJAX requests as follows:
The goal is to have a context-independent function to launch AJAX requests. The data gathered is handled differently based on the context.
This is my function:
function ajaxParameter(routeName, method, array, callback){
//Ajax request on silex route
var URL = routeName;
$.ajax({
type: method,
url: URL,
beforeSend: function(){
DOM.spinner.fadeIn('fast');
},
})
.done(function(response) {
DOM.spinner.fadeOut('fast');
callback(response);
})
.fail(function(error){
var response = [];
response.status = 0;
response.message = "Request failed, error : "+error;
callback(response);
})
}
My problem essentially comes from the fact that my callback function is not defined.
I would like to call the function as such (example)
ajaxParameter(URL_base, 'POST', dataBase, function(response){
if(response.status == 1 ){
console.log('Request succeeded');
}
showMessage(response);
});
I thought of returning response to a variable and deal with it later, but if the request fails or is slow, this won't work (because response will not have been set).
That version would allow me to benefit the .done() and .fail().
EDIT : So there is no mistake, I changed my code a bit. The goal is to be able to deal with a callback function used in both .done() and .fail() context (two separate functions would also work in my case though).
As far as I can see there really is nothing wrong with your script. I've neatened it up a bit here, but it's essentially what you had before:
function ajaxParameter (url, method, data, callback) {
$.ajax({
type: method,
url: url,
data: data,
beforeSend: function(){
DOM.spinner.fadeIn('fast');
}
})
.done( function (response) {
DOM.spinner.fadeOut('fast');
if (callback)
callback(response);
})
.fail( function (error){
var response = [];
response.status = 0;
response.message = "Request failed, error : " + error;
if (callback)
callback(response);
});
}
And now let's go and test it here on JSFiddle.
As you can see (using the JSFiddle AJAX API), it works. So the issue is probably with something else in your script. Are you sure the script you've posted here is the same one you are using in your development environment?
In regards to your error; be absolutely sure that you are passing in the right arguments in the right order to your ajaxParameter function. Here's what I am passing in the fiddle:
the url endpoint (e.g http://example.com/)
the method (e.g 'post')
some data (e.g {foo:'bar'})
the callback (e.g function(response){ };)
Do you mean something like this, passing the success and fail callbacks:
function ajaxParameter(routeName, method, array, success, failure) {
//Ajax request on silex route
var URL = routeName;
$.ajax({
type: method,
url: URL,
beforeSend: function () {
DOM.spinner.fadeIn('fast');
}
}).done(function (response) {
DOM.spinner.fadeOut('fast');
success(response);
}).fail(function (error) {
var response = [];
response.status = 0;
response.message = "Request failed, error : " + error;
failure(response);
})
}
Called like:
ajaxParameter(
URL_base,
'POST',
dataBase,
function(response){
//success function
},
function(response){
// fail function
}
);

Javascript: Break from loop

I want to keep checking a list until it finds an item that is available. For each item in the list it will make a post request to check if the item is available. I want to keep it asynchronous. Also note that i am using ajaxq function not ajax function, that is because i am using this queuing script http://code.google.com/p/jquery-ajaxq/. So it won't be over before it's started sorta-thing. I'm sure thats not the problem though.
I need a way to break out of the loop once a item is available, so i can't just use a callback function because it won't be able to break out of the loop inside a function.
So i thought incrementing a variable if its done and using a do-while loop would work, but it just freezes my browser like it's a never-ending loop.
Any suggestions on how do fix this or do it a better way would be great.
do {
var d = 0;
for(var i in list) {
var item = list[i];
$.ajaxq('queue', {
type: 'POST',
url: baseURL + '/ChangeItem/Check',
data: {
'newItem': item,
'purchaseItem': false
},
error: function(jqXHR, textStatus) {
alert(textStatus);
},
dataType: 'text',
success: function(data) {
if(thisObject.isNotTaken(data)) {
d++;
thisObject.claimItem();
}
}
});
}
} while(d == 0);
You can use a recursive function:
function ChangeItem(list, index) {
var item = list[index];
$.ajaxq('queue', {
type: 'POST',
url: baseURL + '/ChangeItem/Check',
data: { 'newItem': item, 'purchaseItem': false },
error: function(jqXHR, textStatus) { alert(textStatus); },
dataType: 'text',
success: function(data) {
if(thisObject.isNotTaken(data)) { thisObject.claimItem(); doWhateverYouWantNext(); }
else ChangeItem(list, index+1);
}
});
}
The fact that the requests will be queued only guarantees that they will be executed seuqentially, and that the first request will have finished before the second starts. It does not mean that your code to enqueue the second request will wait until the first request is finished. So ajaxq does not help you either way here. You'd have to fall back to a recursive function, that invokes itself from the AJAX callback.
Having said that, you'll notice this'll cause a series of requests to your server, and presumably a series of database lookups. You may find that it'd be a much neater approach to send teh entire list of items to the server, and return the first match from there.
Please try this:
var d = 0;
for(var i in list) {
if(d == 0)
{
var item = list[i];
$.ajaxq('queue', {
type: 'POST',
url: baseURL + '/ChangeItem/Check',
data: {
'newItem': item,
'purchaseItem': false
},
error: function(jqXHR, textStatus) {
alert(textStatus);
},
dataType: 'text',
success: function(data) {
if(thisObject.isNotTaken(data)) {
d++;
thisObject.claimItem();
}
}
});
}
}

Categories

Resources