Trying to make an AJAX call asynchronous in Javascript - 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);
}
}
});

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/

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

How to make multiple HttpRequests with pagination cursors in Parse Cloud

I want to make a HTTP GET request from a url and it will contain the url of next webpage. I have to continue this process till I get an empty "next" url.
My code is as follows:
Parse.Cloud.define("myFunc", fucntion (request, response){
Parse.Cloud.httpRequest({
url: fb_url
}).then(function(httpResponse) {
next_url = httpResponse.data.next_url;
/******************/
// code to make another HttpRequest with next_url and iteratively
// doing it till next_url is null
response.success(httpResponse.text);
}, function(httpResponse) {
response.error("error " + httpResponse);
}
});
I tried a lot of different ways, but all in vain. Can anyone tell me how can I make another HttpRequest with the next_url and keep doing it until next_url is null.
Wrap the http invocation in a function that can be called recursively. This will return a chain of promises that make the requests until null is returned.
function keepGetting(url) {
return Parse.Cloud.httpRequest({ url:url }).then(function(httpResponse) {
nextUrl = httpResponse.data.nextUrl;
return (nextUrl === null)? httpResponse : keepGetting(nextUrl);
});
}
Parse.Cloud.define("myFunc", fucntion (request, response){
// initialize fb_url somehow
keepGetting(fb_url).then(function(result) {
response.success(result);
}, function(error) {
response.error(error);
});
});
(Careful, if the service takes too long or returns too many results before null, your parse call will timeout)

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

how to handle ajax parsererror nicely?

I have a google ad project which requires me to get multiple "setTargeting('', '');" from an object array on json file (via url) --> {"country": "Netherlands", "city": "Amsterdam" }. So far everything works; however, suppose the network fails, or requested JSON parse failed, etc - I'd like to pass an empty array to make sure that the slot will still show ads with no targeting.
What would be a good practise for it?
Advertisements.cachedCategoriesByUrl = {};
Advertisements.getCategories = function(categoriesUrl) {
var cachedCategories = Advertisements.cachedCategoriesByUrl[categoriesUrl];
if(cachedCategories){
return cachedCategories;
} else {
var getCategories = $.ajax({
url: categoriesUrl,
data: { format: 'json' },
error: function(jqXHR, status, thrownError) {
=> I'd like to pass an empty array so the slot will show
ads with no targetting set.
However this doesn't seem to be working.
Do I need to do callback?
}
});
Advertisements.cachedCategoriesByUrl[categoriesUrl] = getCategories;
return getCategories;
}
}
Note:
return getCategories runs before the ajax call finishes. How do I make sure that return getCategories gets my error update(I want to pass an empty array if JSON request fails or invalid). Sorry I am in the learning process.
Solved it with this:
$.ajax({
url: ad.categoriesUrl
}).then(function(data){
Advertisements.advertisementSlot(ad, data);
}, function(data){
data = {};
Advertisements.advertisementSlot(ad, data);
});

Categories

Resources