Getting correct data when using multiple deferred ajax calls - javascript

I have a function that uses two ajax calls in order to get the proper information:
var getUsers = function() {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(foo) {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(bar) {
return foo['age'] = bar.type;
});
});
}
And an outside function that calls the current function and only continues when the calls are finished.
getUsers().then(function(result) {
// ...
});
Now the weird thing is that if I display the result, the 'age' will show up in the console, but if I try to access it using result['age'], it will return undefined.
Is there a proper way of handling multiple deferred calls?
Code
http://codepen.io/norbiu/pen/bNRQxL

Edit Instead of using a separate deferred, you can chain the ones returned from getJSON() like this
var getUsers = function() {
var foo;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.then(function(data) {
foo = data;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
}).then(function(bar) {
foo['age'] = bar.type;
return foo;
});
}
Note: you need to save the return value from the first call or it won't be accessible to the second.
Original code for posterity
You can use a jQuery Deferred object and return that instead
var getUsers = function() {
var dfd = $.Deferred();
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(foo) {
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(bar) {
foo['age'] = bar.type;
dfd.resolve(foo);
}).fail(function(e) {
dfd.reject(e);
})
}).fail(function(e) {
dfd.reject(e);
});
return dfd.promise();
}
http://codepen.io/anon/pen/pvwqZo
The deferred object won't resolve until both requests succeed (and will fail if any of them fail).

Related

How can I redirect a promise to an existing deferred?

I have a function which returns a promise. I create a jQuery deferred for this purpose, which might be resolved/rejected in custom ways, depending on implementation.
One implementation uses an AJAX call, and there I'd like to redirect or queue the failure/resolution of the AJAX promise to the one which was created earlier. This means that whenever the AJAX call has a resolve/reject/progress, the deferred should trigger its own resolve/reject/progress too with the same arguments.
Here is some dummy sample code.
function Test() {
}
Test.prototype.doSomething() {
this._deferred = $.Deferred();
this.doSomethingImpl();
return this._deferred;
}
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
// resolve/reject/progress here the this._deferred based on the ajax promise
}
I know I can do it in a verbose way using the AJAX done, fail and progress callbacks, and manually call my deferred's corresponding method (resolve, reject or progress), but I'm seeking for kind of a one-liner, if there is any.
EDIT
Here is a code which is similar to the real one, using knockoutjs.
function GridViewModel() {
var self = this;
self.pageIndex = ko.observable(0);
...
self._refreshRequest = ko.observable(null).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } });
self._doRefresh = function() {
$.ajax(...)
.done(result) { // update rows, etc. }
.then(
function(r) { self._refreshPromise.resolve(r); },
function(r) { self._refreshPromise.reject(r); },
function(r) { self._refreshPromise.progress(r); }
)
.always(function() { self._refreshPromise = null; }
// here I used the obvious verbose redirecting
}
...
ko.computed(function() {
var pageIndex = self.pageIndex();
if (ko.computedContext.isInitial()) return;
this.refreshRequest("Paging");
});
ko.computed(function() {
var refreshRequest = self.refreshRequest();
if (ko.computedContext.isInitial() || !refreshRequest) return;
self._doRefresh(refreshRequest);
}
}
GridViewModel.prototype.Refresh = function(type) {
this._refreshPromise = this._refreshPromise || $.Deferred();
this._refreshRequest(type);
return this._refreshPromise;
}
This code is a snippet of a complex data grid viewmodel class, and the fancy refresh solution is there to ensure that refreshing is throttled.
Yes, it would be possible to redirect the resolution (in a perfect world1, just deferred.resolve(promise)), but it's completely unnecessary. Don't create deferreds when you're already calling something that produces a promise for you - avoid the deferred antipattern! You can simply return that very promise:
Test.prototype.doSomething = function() {
return this.doSomethingImpl();
};
var test = new Test();
test.doSomethingImpl = function() {
var ajax = $.post(...);
return ajax; // the promise
};
1) where jQuery follows the Promises/A+ specification and deferred.resolve accepts thenables

using a getter to bind a function that returns a promise object

Using javascript getters is a cool way to bind function calls to object definitions. I've used it a number of times before, but in a recent problem I wanted to use it to bind a function that returns a jQuery promise object that gets used in a $.when...$.then chain.
It seems as though using a getter doesn't work the way I was expecting it to; specifically, $.then() doesn't wait for the bound function's promise object to be resolved when it uses the function definition that the getter returns. However, if no getter is used and I just call the function directly, things work as expected.
Any idea why this might be the case?
Example:
var myObj = {
asynch1: {
a: 200,
b: 300,
get runAsynch3() {return function() {
myCustomAsynchCode3(this.a, this.b);
}
},
},
};
function myCustomAsynchCode1() {
var df = $.Deferred();
// do stuff that, when done, calls
// df.resolve();
return df.promise();
}
function myCustomAsynchCode2() {
var df = $.Deferred();
// do stuff that, when done, calls
// df.resolve();
return df.promise();
}
function myCustomAsynchCode3(val1, val2) {
var df = $.Deferred();
// do stuff that, when done, calls
// df.resolve();
return df.promise();
}
If I make a call such as
var that = this;
$.when(myCustomAsynchCode1()).
then(function() {
return that.myCustomAsynchCode2();
).
then(function() {
return that.myObj.runAsynch3();
});
runAsynch3() executes before myCustomAsynchCode2() resolves its promise object.
But, if I make a call like this
var that = this;
$.when(myCustomAsynchCode1()).
then(function() {
return that.myCustomAsynchCode2();
).
then(function() {
return myCustomAsynchCode3(that.myObj.a, that.myObj.b);
});
everything works as expected - the promise objects are resolved without stepping on each other.

jquery deferred fallback - possible scoping issue

I am trying to listen to an array of deferred requests for completion. I want to add in fallbacks so that if the initial url fails, then it will load a json file. (I've loaded the jsbin page here to stop any cross domain issues).
My original code was something like
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail(function() {
$.get("http://run.jsbin.com/")
.done(function() {
statsDeferred.resolve();
});
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
However it fails at the line statsDeferred.resolve();
http://jsbin.com/pofejotu/1/
I have tried adding in $.proxy calls to maintain scope but it isn't working.
function makeCalls() {
var deferreds = [];
var statsDeferred =
$.get("http://thiswillfail.yesitwill");
statsDeferred.fail($.proxy(function() {
$.get("http://run.jsbin.com/")
.done($.proxy(function() {
statsDeferred.resolve();
}, this));
}, this));
deferreds.push(statsDeferred);
return deferreds;
}
var deferreds = makeCalls();
$.when.apply(null, deferreds).done(function() {
alert("done");
});
http://jsbin.com/vonibuhe/1/edit
Both fail on
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
If you want to chain promises, the correct method to use is .then() :
function makeCalls () {
var statsDeferred = $.get("http://thiswillfail.yesitwill");
statsDeferred = statsDeferred.then(
null, /* on success, keep the initial promise's state */
function(){ return $.get("http://run.jsbin.com/"); }
);
return statsDeferred;
}
statsDeferred.resolve();
Uncaught TypeError: undefined is not a function
The error you have is the difference between a Deferred and a Promise.
a Deferred exposes methods to change its inner state (.resolve and .reject),
a Promise only allows you to consult this state, and react on it (.done, .fail, ...)
API functions will generally return a Promise, so that external users cannot meddle with the expected state. As an example, one way to "fix" your code would be the following :
function makeCalls() {
// make a deferred, you will be the one in control of its state :
var deferred = $.Deferred();
var firstGet = $.get("http://thiswillfail.yesitwill");
firstGet.done(function(response) { deferred.resolve(response); })
// if the first request fails, run the second :
firstGet.fail(function(){
var secondGet = $.get("http://run.jsbin.com/");
secondGet.done(function(response) { deferred.resolve(response) };
secondGet.fail(function() { deferred.reject() });
});
// only return the Promise to the outer world :
return deferred.promise();
}

jquery custom deferred functions

I have three functions i'm trying to run, the first two are doing some async stuff that need data for the third to use. I want the third function to fire only when 1 and 2 are both done. this is the general structure but the final function is firing before 1 and 2 finish.
function run() {
var data1 = {};
var data2 = {};
$.when(first(), second()).done(constructData());
function first() {
var d = new $.Deferred();
//do a bunch of stuff async
data1 = {};
d.resolve();
}
function second() {
var d = new $.Deferred();
//do a bunch of stuff async
data2 = {};
d.resolve();
}
function constructData() {
//do stuff with data1 and data2
}
}
Answer was to not call construct data immediately
$.when(first(), second()).done(constructData);
You should return promise object. You also have an error in this line:
$.when(first(), second()).done(constructData());
it should be
$.when(first(), second()).done(constructData); // don't call constructData immediately
So all together it could be:
function run() {
var data1 = {};
var data2 = {};
$.when(first(), second()).done(constructData);
function first() {
return $.Deferred(function() { // <-- see returning Deferred object
var self = this;
setTimeout(function() { // <-- example of some async operation
data1 = {func: 'first', data: true};
self.resolve(); // <-- call resolve method once async is done
}, 2000);
});
}
function second() {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
data2 = {func: 'second', data: true};
self.resolve();
}, 3000);
});
}
function constructData() {
//do stuff with data1 and data2
console.log(data1, data2);
}
}
http://jsfiddle.net/FwXZC/
I think you should have first() and second() return a promise: return d.promise();. From the docs:
If a single argument is passed to jQuery.when and it is not a Deferred or a Promise, it will be treated as a resolved Deferred and any doneCallbacks attached will be executed immediately.
I suspect this might be why the when call is calling constructData too soon.
It's hard to tell from you code, but be sure you are calling d.resolve() after the async operations have completed.
You might find that a more natural approach to explicitly setting data1 and data2 is instead to use the data that is supplied when resolve is called. This would mean that your when call would look something like this:
$.when(first(), second()).done(function(result1, result2) {
data1 = result1[0];
data2 = result2[0];
constructData();
});
Note that the exact format of results supplied to the done method depends on the nature of the deferred objects. If the promises are returned from a call to $.ajax, the results should be of the form [data, statusText, jqXhrObject].

Returning data from $.when with multiple $.getJSON calls

In the following code, I am retrieving data using $.getJSON (returned form the repository) and $.when as the last call is dependent on data from the first:
var getData =
function () {
var data = { userData: null, userTitles: null, userPage: null };
$.when(repository.getUserDetails().done(f1)),
repository.getUserPolicyTitles().done(f2)
.then(repository.getUserPage().done(f3));
function f1(userData) { data.userData = userData; console.log(data.userData) };
function f2(userTitles) { data.userTitles = userTitles; console.log(data.userTitles) };
function f3(userPage) { data.userPage = userPage; console.log(data.userPage) };
return data;
}
return {
getData: getData
};
Most of this works fine. However, I would like to return the data back to a calling module but it returns before the data is ready as I suppose you would expect.
What is the best way to achieve this?
Thanks
Davy
Your usage of deferred seems incorrect. This is my interpretation.
Also, you need to consider that once you start invoking asynchronous methods, you can't make a synchronous return call, which is what you're doing. Instead of returning data, you need to return a promise; then you provide the promise with a callback that will do stuff with the data
var getData = function () {
var myDeferred = $.Deferred();
var data = { userData: null, userTitles: null, userPage: null };
$.when(repository.getUserDetails().done(f1),
repository.getUserPolicyTitles().done(f2),
repository.getUserPage().done(f3)).then(
function(){ myDeferred.resolve(data); },
function(){ myDeferred.reject.apply(myDeferred, arguments); });
//... f1, f2, f3
return myDeferred.promise();
}
return {
getData: getData
};
Then, when you want to actually use the data, you do
your_library.getData().then(function(data){
// magic goes here
}, function(errors_of_some_sort) {
// error handling magic goes here
});
When using $.when, you can only get the data after all your AJAX calls have returned. So for example:
$.when($.getJSON('somewhere'), $.getJSON('somewhere2'))
.done(function(r1, r2){
//set your data object with the responses from the server here
});
So the short answer is you can't "return" the data you retrieved from the server, but you can assign a callback to use the data (or set to some entity of your module) when the data is ready.

Categories

Resources