How to know which $.ajax promise has resolved? - javascript

I'm using jQuery ajax to request data which will then be made into different kinds of charts or tables.
I've put the queries I want to run into an object and send the requests. runQuery() returns a jQuery promise. The data returned when the promise is done is correct. [Edit]Since the ajax requests run asynchronously they may not come back in the order they were issued [/EDIT] and I have no way of know which query the returned data was for.
function refreshData(){
for(var key in baseQueries){
runQuery(baseQueries[key])
.done(function(data){
console.log("Query data for "+key);
// want to call different charting functions
// based upon which results are returned
});
}
};
runQuery(obj) { // "public" function
var params = $.extend({},defaults,obj);
return sendRequest(queryUrl,params)
}
sendRequest(url,data,method){ // "private" function
method = method || "GET";
return $.ajax({
type:method,
url:url,
dataType:"json",
data:data
})
.fail(function(error){
console.log(error);
});
}
In this case the console logs the value of key during the last iteration over the baseQueries object. For example if there are three items in my baseQueries object and the the last item in my baseQueries object is
"This-is-the-last-query":"queryparam1,queryparam2,etc"
Then when all three of the ajax calls resolve I get three logs of "This-is-the-last-query". Which is not helpful because it doesn't tell me which query the data belongs to.
This is similar to the idea of the infamous javascript loop issue but I really don't see how the answer of attaching the value to a DOM element could be used here.
How do I match up which query call goes with which promise? How to I pass the key through the ajax call and return it with the promise data.
Edit
Don't think this is a duplicate of the indicated thread. I see how they are related, but not how to use that to solve this. Suggested duplicate doesn't mention jquery, ajax, promises, or asynchronous issues. It is also marked as a duplicate for another thread that doesn't mention any of those things either.
The solution shown either involves using the dom element to hold the information (which doesn't apply here) needed for the onclick or by adding a closure, but I don't see how to do that when there is already data being returned.

If you pass jQuery ajax a parameter that it knows nothing about, it ignores it and you can access it later.
Here we set a parameter for your extra value (mykey) then we have access to it later for the instance we are using:
function refreshData() {
for (var key in baseQueries) {
runQuery(baseQueries[key], key)
.done(function(data) {
console.log("Query data for " + this.myKey);
// "this" here is the ajax for which the promise (ajax) gets resolved,
// and we return that promise so it works here.
// want to call different charting functions
// based upon which results are returned
});
}
};
runQuery(obj,key) { // "public" function
var params = $.extend({}, defaults, obj);
return sendRequest(queryUrl, params,,key)
}
sendRequest(url, data, method, key) { // "private" function
method = method || "GET";
return $.ajax({
type: method,
url: url,
dataType: "json",
data: data,
myKey:key
})
.fail(function(error) {
console.log(error);
});
}

if you want to check if jquery promise is resolved you can check with jqueryPromise.state() which returns either pending, resolved or rejected depending on state.
If you are sending multiple ajax requests and you want to know when they are completed you can put them into array ([$.ajax(...),$.ajax(...)]) and pass it to
$.when like
var requests = [$.ajax(...),$.ajax(...)];
$.when.apply($, requests).done(function() {
console.log('all requests completed');
});
if you want to build something complex with promises I would suggest using bluebird or less complex rsvp

Related

Abortable promises and using paged data from an API for an autocomplete text field

Disclaimer: I'm new to ES6 and promises in general so its possible my approach is fundamentally wrong.
The Problem
I have an api that returns paged data. That is it returns a certain number of objects for a collection and if there's more data it returns a Next property in the response body with the url to get the next set of data. The data will eventually feed the autocomplete on a text input.
To be specific, if my collection endpoint is called /Tickets the response would look like:
{
Next: "/Tickets?skip=100&take=100"
Items: [ ... an array of the first 100 items ... ]
}
My current solution to get all the ticket data would be to
Make a new promise for the returning the whole combined set of data
"Loop" the ajax calls by chaining dones until there is no more Next value
Resolve the promise
getTickets(filterValue) {
let fullSetPromise = new Promise();
let fullSet = [];
// This gets called recursively
let append = function(previousResult) {
fullSet.concat(previousResult.Items);
// Loop!
if(previousResult.Next) {
$.ajax({
url: previousResult.Next
})
.done(append)
.catch(reason => fullSetPromise.reject(reason));
}
else {
fullSetPromise.resolve(fullSet);
}
}
// We set things off by making the request for the first 100
$.ajax({
url: `/Tickets?skip=0&take=100&filter=${filterValue}`
})
.done(append)
.catch(reason => fullSetPromise.reject(reason));
return fullSetPromise;
}
Eventually the promise is used by the frontend for autocomplete on a text input. Ideally I'd like to be able to abort the previous call when new input comes in.
inputChanged(e) {
this.oldTicketPromise.abort();
this.oldTicketPromise =
Api.GetTickets(e.target.value).then(updateListWithResults);
}
I am aware of debouncing. But that just means the problem happens every n seconds instead of on every key press.
I know the jqxhr object has an abort() property on it and I'd like that to be available to the caller somehow. But because there are multiple jqXHR objects used in GetTickets I'm not sure how to handle this.
So my main 2 questions are:
What is the appropriate way to consume paged data from an api while returning a promise.
How can the returned promise be made abortable?
Side question:
I feel like if I don't catch the errors then my "wrapper" promise will swallow any thrown errors. Is that a correct assumption?
Note the javascript code might have errors. It's mostly demonstrative for the logic.
Edit: Solved
I have solved this by combining this answer https://stackoverflow.com/a/30235261/730326 with an array of xhrs as suggested in the comments. I will add a proper answer with code when I find the time.

How to make an ajax call for each element of an array of objects, using promises?

I have this code:
// _arrOfUsers = array of objects, each object represents a user.
//For example, a single object may be something like {name:"mike", id:"123"}
var _successUsers = function (_arrOfUsers) {
var promises = []; //empty array of promises
_arrOfUsers.forEach(function (user) {
promises.push(function () {
return $.Deferred(function (dfd) {
$.post("//sessionID",
queryServ.getAllUserDomainCommentsFunc("//currentDomainID", user.iD, "//currentDomainID", "//currentURL"),
'text', 'text/plain', this,
function (dataX) {
dfd.resolve(dataX);
});
}).promise();
});
});
$.when(promises).then(function (results) {
console.log(results);
});
};
Some elements to understand this code:
**** Ajax call settings
url: sessionURL
data: data to send to the server is an external call to the module
queryServ.js and the function getAllUserDomainCommentsFunct that
returns a JSON object. It needs these parameters to work:
currentDomain, userID, currentDomain (again), currentURL
type: text
contentType: text/plain
context: this
**** A comment is a jsonObject smilar to {id: "998", textOfcomment:"I commented this", target: "//targetID"}.
What I want to do is to make an ajax call for each user of the array "_arrOfUsers" to extract all his comments on the server; after all the "extractions" I need to get all the results (that are all the comments from all users) and print them in the console. The order is important for my code, this is why I'm using Promises.
I found the structure of this algorithm on a JSfiddle here on StackOverflow, but it doesn't work for me because it returns an array of functions and I can't understand why, probably because I've never used Promises during my career.
Thank you in advance, if there's something not clear please feel free to tell me.
There's no need to create a deferred here since $.post() already returns a promise and $.when() keeps the results in order for you. Instead, you can do this:
$.when.apply($, _arrOfUsers.map(function(user) {
return $.post("//sessionID", queryServ.getAllUserDomainCommentsFunc("//currentDomainID", user.iD, "//currentDomainID", "//currentURL"));
})).then(function() {
// all results available in order here as arguments to this callback
// arguments[0][0] is the first result
// arguments[1][0] is the second result
// arguments[2][0] is the third result and so on
for (var i = 0; i < arguments.length; i++) {
// arguments[i][0] is next one of your results
}
});
Note: I didn't quite understand what you were trying to do with the arguments to $.post() (you seem to have made up some arguments the jQuery doc doesn't show) so I simplified it somewhat, but you may need to tweak those $.post() arguments to suit your purpose.
Personally, I despise $.when() and how it interacts with jQuery ajax promises (very confusing to use) so I would cast the ajax promise to an ES6 promise and use Promise.all() (using Bluebird for promise support if older browser support is needed).
Promise.all(_arrOfUsers.map(function(user) {
return Promise.resolve($.post("//sessionID", queryServ.getAllUserDomainCommentsFunc("//currentDomainID", user.iD, "//currentDomainID", "//currentURL")));
})).then(function(results) {
// results is an array of results in order
});
Or if you include Bluebird, you can use:
Promise.map(_arrOfUsers, function(user) {
return Promise.resolve($.post("//sessionID", queryServ.getAllUserDomainCommentsFunc("//currentDomainID", user.iD, "//currentDomainID", "//currentURL")));
}).then(function(results) {
// results is an array of results in order
});

Why does angular $resource add extra objects ($promise, $resolve...) to my data response?

I return a resource with a URL
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
function(error){ $scope.msg = "error" } );
Resource returns
["item1"...."item_n",.....,"$promise", "$resolved", "$get", "$save", "$query", "$remove", "$delete"]
Why do I get all those objects in my data set. I'm guessing $promise just returns all this and waits for the server response. But once I have the server response where can I just get my server data without the Promise jargon?
If you look at the angular source here:
https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L505
There is a toJSON method on the Resource prototype chain that will accomplish this for you.
For example:
$resource("http://foo.com/bar.json").get(function(res) {
$scope.result = res.toJSON();
});
You need to return wrapped result like {'result': { 'some_key': 'some_val' }} from your backend.
Or just do like described above.
Diary.getSharedWithMe(function(data) {
delete data.$promise;
delete data.$resolved;
_self.sharedDiariesWithMe = data;
}, function(error) {
console.log(error)
});
$resource returns an object or array that will have your data when the call completes. All those functions are there to help you out and $resource is mainly intended for CRUD operations. If you want the data, you have to wait for it to get returned so you might as well use the promise. If you want to strip all of those properties you can use angular.toJson to convert it to json, but angular does that for you when posting it back to a resource or $http call so you shouldn't have to.
$scope.data = $resource("http://foo.com/bar.json").get();
// $scope.data does not have your data yet, it will be
// populated with your data when the AJAX call completes
...
// later in a call from a save button maybe you can just do
// this to post your changes back:
$scope.data.$save();
So in case someone else is stumbling here and didn't understand promises/angularjs here is what is going on. When you use .then() or .get() you get a promise and some helper functions all in the same object. This is awesome because then you don't worry about callbacks being defined and whether data is available because the promise object always has some properties. This object contains your raw data in another object within. So the promise object is nested, you just have to reference the data object within when the data is ready.
Here's what I was doing
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
//data is actually a promise object.
function(error){ $scope.msg = "error" } );
promise object
Note the data is actually under another object called "data". So in your success callback to get just the data you should do in this case: data.data
To automatically remove them from every request, you can add an interceptor:
angular.module('app').config(config);
config.$inject = ['$httpProvider'];
function config($httpProvider) {
$httpProvider.interceptors.push(interceptor);
}
interceptor.$inject = [];
function interceptor() {
return {
request: (config) => {
if (config.data) {
delete config.data.$promise;
delete config.data.$resolved;
}
return config;
}
};
}

Modifying JSONP results before success callback

I'd like to load some JSON data from an external service. However, it provides
{ foo: ..., bar: ..., useful: {...} }
and what I really care about is in the "useful" part. I need to pass just that part to the success callback.
I'm trying to use Deferred to load from multiple data sources at once -- something like this. I'd like to retrieve the data, "massage" the result, and have just the "useful" part in the example above actually passed to the then callback. My understanding is that when you pass a Deferred to when(), the data goes directly to the callback passed to then(), so I need to hook into the process before it gets there.
I tried dataFilter but for JSONP that's not possible. Is there any other way to intercept these results? I can add some checks to my then() callback to handle cached data differently from the "fresh" results, but that sort of loses the magic of Deferred in the first place.
To clarify, this doesn't work:
$.when($.ajax({
url: "host/service",
dataType: "jsonp",
dataFilter: function(data, type){
return data.useful; // throws, data === undefined
}
})).then(function(usefulStuff){ ... });
You can call .pipe() to process the data and create a new Deferred:
$.getJSON(...).pipe(function(results) {
return ...;
})

Chaining Promises recursively

I'm working on a simple Windows 8 app in which I need to fetch a set of data from a web site. I am using WinJS.xhr() to retrieve this data, which returns a Promise. I then pass a callback into this Promise's .then() method, which supplies my callback with the returned value from the asynchronous call. The .then() method returns another Promise, giving it the value that my callback returns. The basic structure of such a query would be as follows:
WinJS.xhr({ url: "http://www.example.com/" }).then(
function callback( result_from_xhr )
{
//do stuff
return some_value;
}).then(
function secondcallback( some_value )
{
//do stuff
});
In my situation, however, I may need to make additional queries for data depending on the data returned by the first query, and possibly more queries depending on THAT data... and so on, recursively.
I need a way to code this such that the final .then() is not executed until ALL the recursions have completed, similar to this:
function recurse() {
return WinJS.xhr({ url: "http://www.example.com/" }).then(
function callback( result_from_xhr )
{
if( result_from_xhr == something )
{
recurse();
}
});
}
recurse().then(
function final()
{
//finishing code
});
The problem is that, of course, the finishing code is called as soon as the first level of recursion completes. I need some way to nest the new promise and the old promise from within the callback.
I hope my question is clear enough, I'm really not sure how to explain it and frankly the idea of asynchronous recursive code makes my head hurt.
What I would do here is to create a whole new, standalone promise that you can complete manually, and return that from the recurse() function. Then, when you hit the point that you know you're done doing async work, complete that promise.
Promise.join works when you've got a known set of promises - you need the entire array of promises available before you call join. If I followed the original question, you have a variable number of promises, with more possibly popping up as part of async work. Join isn't the right tool in these circumstances.
So, what does this look like? Something like this:
function doSomethingAsync() {
return new WinJS.Promise(function (resolve, reject) {
function recurse() {
WinJS.xhr({ url: "http://www.example.com/" })
.then(function onResult(result_from_xhr) {
if (result_from_xhr === something) {
recurse();
} else {
// Done with processing, trigger the final promise
resolve(whateverValue);
},
function onError(err) {
// Fail everything if one of the requests fails, may not be
// the right thing depending on your requirements
reject(err);
});
}
// Kick off the async work
recurse();
});
}
doSomethingAsync().then(
function final()
{
//finishing code
});
I rearranged where the recursion is happening so that we aren't recreating a new promise every time, thus the nested recurse() function instead of having it at the outer level.
I solved this problem in perhaps a different way (I think it's the same problem) while making my own Windows 8 app.
The reason I came across this problem is because, by default, a query to a table will paginate, and only return 50 results at a time, so I created a pattern to get the first 50, and then the next 50, etc, until a response comes back with less than 50 results, and then resolve the promise.
This code isn't going to be real code, just for illustration:
function getAllRows() {
return new WinJS.Promise(function(resolve, reject){
var rows = [];
var recursivelyGetRows = function(skipRows) {
table.skip(skipRows).read()
.then(function(results){
rows = rows.concat(results);
if (results.length < 50) {
resolve(rows);
} else {
recursivelyGetRows(skipRows + 50);
}
})
}
recursivelyGetRows(0);
});
}
so getAllRows() returns a promise that resolves only after we get a result back with less than 50 results (which indicates it's the last page).
Depending on your use case, you'll probably want to throw an error handler in there too.
In case it's unclear, 'table' is a Mobile Services table.
You will need to use Promise.join().done() pattern. Pass an array of Promises to join(), which in your case would be a collection of xhr calls. The join() will only call done() when all of the xhr Promises have completed. You will get an array of results passed to the done(), which you can then iterate over and start again with a new Promise.join().done() call. The thing to be away of, when using this approach, is that if one of the Promises passed to join() fail, the entire operation is treated as an error condition.
Sorry I don't have time right now to try and stub out the code for you. If I get a chance, I will try to later. But you should be able to insert this into your recursive function and get things to work.
Well, I solved my problem; my recursive function was misinterpreting the data and thus never stopped recursing. Thank you for your help, and I'll be sure to watch those screencasts, as I still don't quite fully grasp the Promise chaining structure.

Categories