Turn several ajax requests into Observables with RxJS - javascript

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

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

jQuery Ajax promise queue not working when success function fails

I have a single-page app that uses a queuing mechanism based on promise, something like this:
a) a function that handles ajax requests
function AjaxSender(SomeAjaxData, FunctionToCallBack, SomeCallBackData) {
return $.ajax({
url: ...,
type: "POST",
data: SomeAjaxData,
success: function (msg, textStatus, request) {
if (FunctionToCallBack) {
FunctionToCallBack(SomeCallBackData);
//problem if there's a bug when this executes
}
}
});
}
b) a function that uses a promise object to queue requests
var AppAjaxPromise;
function AjaxRequestQueue(SomeAjaxData, FunctionToCallBack, SomeCallBackData) {
if (AppAjaxPromise) {
AppAjaxPromise = AppAjaxPromise.then(function () {
return AjaxSender(SomeAjaxData, FunctionToCallBack, SomeCallBackData);
});
return AppAjaxPromise;
}
AppAjaxPromise = AjaxSender(SomeAjaxData, FunctionToCallBack, SomeCallBackData);
return AppAjaxPromise;
}
When I want to send an ajax request, I call AjaxRequestQueue(TheAjaxData, TheFunctionToCallBack, TheCallBackData) and the queuing mechanism ensures that if multiple requests are sent simultaneously, or before one has finished returning, they are queued and processed one after the previous one is done.
The problem occurs when a bug stops the execution of the callback function. If that function bugs, the whole queuing mechanism stops and calling AjaxRequestQueue doesn't trigger ajax requests any more.
What do I need to do to fix this?
As jQuery's $.ajax returns a promise (and since you are using it), abandon the use of the success callback. Instead move that code in a then callback. This will allow you to chain a catch method (jQuery 3.x) call to it to respond to errors. If you don't trigger another error in that catch callback, the promise it returns will be resolved again (not rejected), so the rest of your chain will not be aborted:
function ajaxSender(someAjaxData, functionToCallBack, someCallBackData) {
return $.ajax({
url: ...,
type: "POST",
data: someAjaxData
}).then(function (msg, textStatus, request) {
if (functionToCallBack) {
functionToCallBack(someCallBackData);
}
}).catch(function (err) {
console.log('error occurred, but request queue will not be interrupted', err);
});
}
jQuery 2.x
The above needs jQuery 3.x. In jQuery versions before 3.x, you can replace the catch method like this (notice the null argument):
...
}).then(null, function (err) {
...
...but jQuery 2.x promises are not Promise/A+ compliant, which makes it a pain to get it right. Here is how you could do it for jQuery 2.x. This snippet uses a URL that mimics a delay and an HTTP response status code, which allows it to test request errors, JavaScript run time errors, and sequencing:
function ajaxSender(someAjaxData, functionToCallBack, someCallBackData) {
return $.ajax({
// URL for demo: server will use the sleep parameter in the data,
// and will return the given HTTP status
url: "http://httpstat.us/" + someAjaxData.status,
type: "GET", // The demo URL needs a GET
data: someAjaxData
}).then(function (data) {
if (functionToCallBack) {
try { // Would not be necessary if jQuery 2.x were Promise/A+ compliant
functionToCallBack(someCallBackData);
} catch (e) {
console.log(someCallBackData, 'Error occurred during callback');
}
}
}, function (err) { // This second function captures ajax errors
console.log(someCallBackData, 'HTTP error');
// Return a resolved promise.
// This would not be necessary if jQuery 2.x were Promise/A+ compliant
return $.when();
}); // In jQuery 3.x you would chain a catch call here instead of the try/catch.
}
var appAjaxPromise = $.when();
function ajaxRequestQueue(someAjaxData, functionToCallBack, someCallBackData) {
appAjaxPromise = appAjaxPromise.then(function () {
return ajaxSender(someAjaxData, functionToCallBack, someCallBackData);
});
return appAjaxPromise;
}
// Demo: the ajax data argument is also used to define the HTTP response status and
// the sleep time, and the data argument identifies the number of the call
// Survive an HTTP error
ajaxRequestQueue({ status: 404, sleep: 1000 }, myCallBack, 1);
// Survive a runtime error in the callback
ajaxRequestQueue({ status: 200, sleep: 2000 }, myErrorGeneratingCallBack, 2);
// Demo that the callback calls remain in the right order
ajaxRequestQueue({ status: 200, sleep: 3000 }, myCallBack, 3);
ajaxRequestQueue({ status: 200, sleep: 2000 }, myCallBack, 4);
ajaxRequestQueue({ status: 200, sleep: 1000 }, myCallBack, 5);
function myCallBack(data) {
console.log(data, "My callback is called");
}
function myErrorGeneratingCallBack(data) {
console.log(data, "My callback is called");
throw "I threw an error in my callback";
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You can still continue the above pattern when moving to jQuery 3: it will still work. But ideally, you should then migrate the code to the catch-based version that I provided at the top.
Some other remarks
There is a consensus to only capitalise the first letter of a variable when it is a constructor/class.
By initialising appAjaxPromise as an immediately resolved promise, you can avoid code repetition:
var appAjaxPromise = $.when();
function ajaxRequestQueue(someAjaxData, functionToCallBack, someCallBackData) {
appAjaxPromise = appAjaxPromise.then(function () {
return ajaxSender(someAjaxData, functionToCallBack, someCallBackData);
});
return appAjaxPromise;
}
I am not sure if this answer will fix it, but the callbacks you are using in the success function might not be accessible from there.
You can add extra data to the request like so... and it will be accessible with 'this....'. (see success).
Not sure if you should ether :p I have been doing this to pass data from inside an object without having to change the ajax' context or using $.proxy. Also I have been able to access an object's function that fires a request, from inside that request's success, making it recursive for sending files in chunks.
If there are any comments on doing this, I would love to hear.
return $.ajax({
FunctionToCallBack: FunctionToCallBack,
SomeCallBackData: SomeCallBackData,
url: ...,
type: "POST",
data: SomeAjaxData,
success: function (msg, textStatus, request) {
if (this.FunctionToCallBack) {
this.FunctionToCallBack(this.SomeCallBackData);
//problem if there's a bug when this executes
}
}
});

How to handle ajax response in FLUX

I'm new in FLUX and I have problem how to handle ajax in FLUX.
My situation is following :
I have file commentAPI.js
//all js files are compiled from coffescript
// fetching all comments from server
_fetchComments: function() {
var promise;
promise = $.ajax({
url: "comments/show",
type: "GET",
dataType: "json"
});
return promise.then(function(response) {
// here should be any action ?
}, function(error) {
return console.log(error);
}); }
Then I have commentActions.js
fetchComments: function () {
allcomments=commentAPI._fetchComments();
return Dispatcher.dispatch({
actionType: ActionTypes.ALL_COMMENTS,
comments: allcomments
});
}
This code actually doesnt work because function _fetchComments called in commentActions.js return whole promise.
What I want to do: I would like to get response from ajax callback function and pass the result to my payload object and then dispatch it by Dispatcher in my _fetchComments() function in commentActions.js
How is the best way to do it? How can I get the access to the ajax callback function response ?
You should dispatch _fetchComments function and when that promise is resolved, invoke the action fetchComments.
In the react code you should invoke the async function (i.e. _fetchComments).
In your example:
// fetching all comments from server
_fetchComments: function() {
var promise;
promise = $.ajax({
url: "comments/show",
type: "GET",
dataType: "json"
});
return promise.then(function(response) {
// put the sync action here
fetchComments(response)
}, function(error) {
return console.log(error);
}); }
And don't forget to remove it from the action (i.e fetchComments)

returned object is undefined in a custom ajax service

I created an AngularJS service which does ajax request to the server and returns the response object to the controller. Below is my service
app.factory('ajaxService', function() {
return {
ajaxcall: function(url, type, data, handler) {
$.ajax({
url: url,
type: type,
data: data,
beforeSend: function(xhr) {
xhr.setRequestHeader("X-OCTOBER-REQUEST-HANDLER", handler);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
})
.done(function(response) {
console.log("ajaxService done: ");
console.log(response);
return response;
})
.fail(function(response) {
console.log("in onCheckUser-error: ajaxService ");
});
}
}
});
The controller is defined as below
var app = angular.module('starter', [])
app.controller('myCtrl', function(ajaxService) {
var res = {};
res = ajaxService.ajaxcall("https://www.travelmg.in/check-login",'POST','',"onCheckLogin");
console.log(res);
});
Here, i get the expected response in console in the ajaxService service. When i return the response, i see an "undefined" value in res variable in console.
I don't understand why the res variable is undefined. Please suggest
Thats because your making an asynchronous call, which means it will not return the result immediately.
only way to resolve this is to receive the promise object returned from $.ajax & use the .done() function on it to receive the successful data.
What you need to do:
Move the done() & fail() outside service factory.
return the ajax promise object all the way to the consumer, i.e controller.
JS CODE:
//service factory code
return {
ajaxcall: function(url, type, data, handler) {
return $.ajax({
url: url,
type: type,
data: data,
beforeSend: function(xhr) {
xhr.setRequestHeader("X-OCTOBER-REQUEST-HANDLER", handler);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
});
}
}
//controller code
app.controller('myCtrl', function(ajaxService) {
var res = {};
ajaxService.ajaxcall("https://www.travelmg.in/check- login",'POST','',"onCheckLogin")
.done(function(response) {
console.log("ajaxService done: ");
console.log(response);
//return response; // dont return instead process the result here
})
.fail(function(response) {
console.log("in onCheckUser-error: ajaxService ");
});
});
Note:
I would personally dont prefer mixing jquery and angularjs library unless jquery is really needed for some third party library. both are two different frameworks with different ideology,so dont mix them.
also if your referring to jquery only for $.ajax api ? then i would suggest you to use $http, whose API is same as $.ajax. ref: https://docs.angularjs.org/api/ng/service/$http
You have built the application in angular so it would be convenient to use $http directive to make ajax calls. Inject $http is your service, then you can handle the response as such:
ajaxService.ajaxcall("https://www.travelmg.in/check-login",'POST','',"onCheckLogin").then(function(response) {
console.log(response.data);
});

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

Categories

Resources