ajax deferred chaining call - javascript

I'm a bit confused I am using the result of callone() to modify a global object(I'm not sure of a better way to do this) trying to accomplish this with deferred. By the time time I calltwo() the global object should be modified with the new data
var obj = {};
var id = obj.id;
//global object
$.when(callone(obj)).then(calltwo(id),function(data)
{
});
- Ajax function :1
function callone(requiredData)
{
var d = new $.Deferred();
var ajaxCall1 = $.ajax({
type:"POST",
url: 'AB/',
data: requiredData,
success: function(data) {
//return data to the callee?
d.resolve(p_obj);
//set ID on the object
obj.id = data.id;
return obj;
},
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ': ' + errorThrown);
},
always: function(data) { }
});
}
function calltwo(id from callback one)
{
}

I've included a much simpler implementation below.
callone() must return a deferred or promise for you to wait on it or chain other operations to it and you can just use the promise that $.ajax() already returns rather than creating your own.
Further, there is no reason to use $.when() here because it really only adds value when you're trying to wait on multiple promises running in parallel which isn't your case at all. So, you can just use the .then() handler on the individual promises you already have.
In addition, you really don't want to use globals when processing async operations. You can chain the promises and pass the data right through the promises.
Here's what callone() should look like:
function callone(requiredData) {
return $.ajax({
type: "POST",
url: 'AB/',
data: requiredData
});
}
function calltwo(...) {
// similar to callone
// returns promise from $.ajax()
}
callone(...).then(function(data) {
// when callone is done, use the id from its result
// and pass that to calltwo
return calltwo(data.id);
}).then(function(data) {
// process result from calltwo here
}, function(err) {
// ajax error here
});
Notice that this code isn't creating any new Deferred objects. It's just using the promise that is already returned from $.ajax(). Note also that it isn't use success: or error: handlers either because those also come through the promises.
Also, note that return a promise from within a .then() handler automatically chains it into the previous promise so the previous promise won't be resolved until the newly returned promise is also resolved. This allows you to keep the chain going.
Also, note that returning data from an async callback function does not return data back to the caller of the original function so your attempt to return something from the success: handler was not accomplishing anything. Instead, use promises and return data through the promises as they are specifically designed to get async data back to the .then() handlers.

Related

Wrapping $.ajax with Promise doesn't resolve

What I've been trying is to call $.ajax multiple times, until the result is empty.
Here is a simplified version of my experimentation...
var objects = [];
function getObjects (page){
return new Promise(function(resolve, reject){
$.ajax({
url : url+"&pageNumber="+page,
type:'get',
dataType:'json',
async : true,
}).done(function(results){
if(results.length > 0){
objects = objects.concat(results);
return getObjects(page + 1);
}
console.log("Finished",objects);
resolve(objects);
}).fail(reject);
});
};
var page = 0;
getObjects(page).then(function(objects) {
console.log("getObjects completed",objects);
}).catch(function(error){
console.log(error);
});
Put simply, getObjects(page) will be called repeatedly until all the objects are retrieved.
The first console.log() shows what I expect, but the then function at the end doesn't run.
Interestingly, when I intentionally make the function fail, the catch part works properly. Clearly, resolve isn't working, but I don't see why.
Any advice will be appreciated.
EDIT
I've tried reject and reject(objects), neither worked.
jQuery Ajax requests are promises. They may not be Promise instances, but they implement the Promise interface. There is no need to wrap them.
function getObjects(page, objects) {
page = page || 0;
objects = objects || [];
return $.get(url, {pageNumber: page}).then(function (results) {
objects.push.apply(objects, results);
return results.length ? getObjects(page + 1, objects) : objects;
});
}
getObjects().done(function (objects) {
console.log("getObjects completed",objects);
}).fail(function(error){
console.log(error);
});
Also, in this case there is no reason to use the more wordy $.ajax(). $.get() will do just fine. jQuery will automatically detect a JSON reponse, and Ajax requests are async anyway, so setting the dataType and async parameters is redundant.

Jquery AJAX promise with always break when ajax fail

I have a loop with multiple ajax request.
each ajax is managed thru always callback.
each ajax is pushed in promises array.
finally $.when is used on always.
If all $.ajax got success the $.when.apply($, promises).always(...);
is called when all $.ajax calls succeed.
But for example on 3 $.ajax call the second fails, the $.when.apply($, promises).always(...); is fired only after the second and not when all 3 $.ajax calls suceed.
Any help?
Following the code
$(".upload_all").bind("click", function() {
var confirmed_count = 0;
var error_count = 0;
var promises = [];
to_uploads.each(function(index,val) {
var elem = $(this);
var promise = $.ajax({
url: "/upload/storeUploadedFile",
type: 'POST',
}).always(function(data_or_jqXHR, textStatus, jqXHR_or_errorThrown) {
if (textStatus === "success") {
// ..
} else {
// ..
}
});
promises.push(promise);
});
$.when.apply($, promises)
.always(function() {
console.log("Promises Always! ") // do other stuff
});
}
As #kevin-b mentions, the promise that $.when returns (pretty much the same as Promise.all) will be rejected if any of the promises passed to it is rejected. You probably want this, to inform the user that some uploads where not successful.
But $.ajax(..).always (the same as promise.finally) means 'execute this callback when this promise is either rejected or resolved'. You probably want instead to do something when all ajax call succeed, or do something else when any of them fails. Use Promise.all(promises).then(...).catch(...) for that.
$(".upload_all").bind("click", function() {
var confirmed_count = 0;
var error_count = 0;
var promises = to_uploads.map(function(index,val) { // using map instead of each here saves us a couple of steps
var elem = $(this);
return $.ajax({
url: "/upload/storeUploadedFile",
type: 'POST',
})
.then(function() { confirmed_count += 1; })
.catch(function() {
error_count += 1;
return Promise.reject(); // Return a rejected promise to avoid resolving the parent promise
});
});
// You can use the native API here which does not require doing the `apply` hack
Promise.all(promises)
.then(function() { alert('All uploads successful'); })
.catch(function() { alert(`Successful uploads: ${confirmed_count}. Failed uploads: ${error_count}`); }
}
Remember JQuery uses an alternative implementation of promises, but they still respond to the native API:
.then (.done in JQuery flavor): attach a callback to be run on success
.catch (.fail in JQuery flavor): attach a callback to be run on error. Unless a rejected promise is returned this will resolve the promise.
.finally (.always in JQuery flavor): attach a callback to be run after any other callbacks run, despite of the promise being rejected or resolved.

Continue of failure of jQuery Deferred chain

I'm doing a series of sequential AJAX calls in jQuery, using the usual method of chaining with Deferred. The first call returns a list of values and the subsequent calls are made with those returned list entries. After the first call that returns the list, the subsequent calls may be done in any order, but they must be done one at a time. So this is what I use:
$.when(callWebService()).then(
function (data) {
var looper = $.Deferred().resolve(),
myList = JSON.parse(data);
for (var i in myList) {
(function (i) {
looper = looper.then(function () { // Success
return callWebService();
},
function (jqXHR, textStatus, errorThrown) { // Failure
if (checkIfContinuable(errorThrown) == true)
continueChain();
else
failWithTerribleError();
});
})(i);
}
});
It turns out that the subsequent calls may fail at times, but I still want to do the remainder of the calls. In my listing, that's what this little bit of inventive pseudo code is meant to do:
if (checkIfContinuable(errorThrown) == true)
continueChain();
else
failWithTerribleError();
How on earth do I implement continueChain though? It appears as though a failure on any deferred will cause the rest of the chain to also fail. Instead, I'd like to log the error and continue with the rest of the list.
With Promises/A+ this is as easy as
promise.then(…, function(err) {
if (checkIfContinuable(err))
return valueToConinueWith;
else
throw new TerribleError(err);
})
Unfortunately, jQuery is still not Promises/A+ compliant, and forwards the old value (result or error) - unless you return a jQuery Deferred from the callback. This works just the same way as rejecting from the success handler:
jDeferred.then(…, function(err) {
if (checkIfContinuable(err))
return $.Deferred().resolve(valueToConinueWith);
else
return $.Deferred().reject(new TerribleError(err));
})
Recovering from an error in a jQuery promise chain is more verbose than with Promises/A+ implementations, which naturally catch errors in a .catch's or a .then's error handler. You have to throw/rethrow in order to propagate the error state.
jQuery works the other way round. A .then's error handler (.catch doesn't exist) will naturally propagate the error state. To emulate "catch", you have to return a resolved promise, and the chain will progress down its success path.
Starting with an array of items on which you want to base a series of async calls, it's convenient to use Array.prototype.reduce().
function getWebServiceResults() {
return callWebService().then(function(data) {
var myList;
// This is genuine Javascript try/catch, in case JSON.parse() throws.
try {
myList = JSON.parse(data);
}
catch (error) {
return $.Deferred().reject(error).promise();//must return a promise because that's what the caller expects, whatever happens.
}
//Now use `myList.reduce()` to build a promise chain from the array `myList` and the items it contains.
var promise = myList.reduce(function(promise, item) {
return promise.then(function(arr) {
return callWebService(item).then(function(result) {
arr.push(result);
return arr;
}, function(jqXHR, textStatus, errorThrown) {
if(checkIfContinuable(errorThrown)) {
return $.when(arr); // return a resolved jQuery promise to put promise chain back on the success path.
} else {
return new Error(textStatus);//Although the error state will be naturally propagated, it's generally better to pass on a single js Error object rather than the three-part jqXHR, textStatus, errorThrown set.
}
});
});
}, $.when([])) // starter promise for the reduction, resolved with an empty array
// At this point, `promise` is a promise of an array of results.
return promise.then(null, failWithTerribleError);
});
}
Notes:
An overall function wrapper is assumed, function getWebServiceResults() {...}.
callWebService() is assumed to accept item - ie the contents of each element of myList.
To do its job, checkIfContinuable() must accept at least one argument. It is assumed to accept errorThrown but might equally accept jqXHR or textStatus.

jQuery $.Deferred() with Dynamic Url

I am starting to re-factor some code to use promises and caching to ensure cleaner code. My Code (See below) has tried to use this new premise and is working with slight success. The main issue is that the caching mechanism that is uses by default prevents my from being able to "pass" in a dynamic url value without it returning the same cached results every time. How can i update the code below to use a cache key?
"use strict";
var FLEX = window.FLEX|| {};
FLEX.Following = FLEX.Following|| {};
FLEX.Following.Process = function () {
var deferred = $.Deferred(),
execute = function (followUrl) {
$.ajax(
{
url: _spPageContextInfo.webAbsoluteUrl +
followUrl,
method: "GET",
headers: {
"accept": "application/json;odata=verbose",
},
success: function (data) {
deferred.resolve(data);
},
error: function (err) {
deferred.reject(err);
}
}
);
return deferred;
};
return {
execute: execute
}
}();
FLEX.Following.Init = function (divName, followUrl) {
FLEX.Following.Process.execute(followUrl).promise().then(
//success
function (data) {
var html = "";
$.each(data.d.Followed.results,function(index, value){
html += value.Name + "<br />";
});
$(divName).html(html);
},
//failure
function(err) {
$(divName).html('Failed');
}
);
}
I then call the code using the following lines:
$(document).ready(function() {
FLEX.Following.Init("#followed-sites", "/_api/social.following/my/followed(types=4)");
FLEX.Following.Init("#followed-people", "/_api/social.following/my/followed(types=1)");
FLEX.Following.Init("#followed-documents", "/_api/social.following/my/followed(types=2)");
});
The resulting output "works" however i get the same results in each div as it appears that every subsequent call jQuery gets the cached version from one of them and therefore each result set is identical. How can i ensure that i can use the same functions with the different dynamic urls as above and cache them separately.
Thanks
I think the root problem is that you create a single $.Deferred() and then you try to reuse it for multiple calls to execute() and that won't ever work. A deferred is a one-shot trigger. Once it has been fulfilled or rejected it stays that way and contains the same data forever. If you want a different result on subsequent calls, you have to create a new deferred for each call. So, because you were creating your deferred outside of the execute() function, you only ever had one deferred and thus the first result would just stick forever.
The fix here is to use a separate promise/deferred for each call to execute(). Since $.ajax() already returns a new promise each time you call it - you can use that instead of creating your own $.Deferred manually which is an anti-pattern. Let's refactor and solve both issues at once.
Here's your function refactored so solve those issues:
var execute = function (followUrl) {
return $.ajax({
url: _spPageContextInfo.webAbsoluteUrl + followUrl,
method: "GET",
headers: {"accept": "application/json;odata=verbose"}
});
};
Then, in this line of code:
FLEX.Following.Process.execute(followUrl).promise().then(
You should remove the .promise() because my change above is already returning a promise so this line can just become this:
FLEX.Following.Process.execute(followUrl).then(

How to return a promise from a WinJS function?

I have a function which will generate the WinJS.xhr promise and return the same to the calling function. But after getting the promise, when doing a .then on it, all I'm getting is an empty array!!!
APPROACH 1:
Here is the function which is returning a promise. It's inside a WinJS.Class.define :
getFeaturedData: function () {
var featuredUrl = utils.getRequestUrl(globals.featuredTag, 1, 0);
return WinJS.xhr({ url: featuredUrl });
},
I'm calling that function in home.js and attaching a .then this way:
var promise = MyApp.Services.Movies.getFeaturedData();
promise.then(function(success) {
var data = success;
},
function (error) {
})
The result variable data is always an empty array which I can't seem to understand why.
APPROACH 2:
If I do .then in the getFeaturedData function itself then it works, surprisingly.
getFeaturedData: function () {
var featuredUrl = utils.getRequestUrl(globals.featuredTag, 1, 0);
var promise = WinJS.xhr({ url: featuredUrl });
promise.then(function (success) {
var data = success;
})
},
In this case, data seems to contain proper data returned from the server.
Can anyone explain this behavior? Why the first approach doesn't work and the second one does?
In example 1, getFeaturedData is returning a promise.
In example 2, it returns nothing (i.e. undefined).
However, the timing when "data" is set is unchanged. You're setting the "data" value in the completion handler of the XHR in both cases. Most likely the difference lies in where you had set your breakpoint. In neither case will "data" be set at the end of getFeaturedData, or at the end of the calling block of code. Instead, it will be set sometime later when the XHR promise completes.
A couple other notes:
Approach 2 can return the result of promise.then() (which is another promise) so that the caller can actually schedule work to happen once data is set.
In both cases, the "promise" variable is kind of extraneous, or it could be set to the result of either getFeaturedData().then(), or xhr().then().
I don't if it is a typo but your first approach is missing an ')' at the end of the function error to enclose the promise result.
Other than that, I can't see why the 1st approach wouldn't work.
Having said that, here's how I would write it:
getFeaturedData: function () {
return new WinJS.Promise(function (complete, fail){
var featuredUrl = utils.getRequestUrl(globals.featuredTag, 1, 0);
WinJS.xhr({ url: featuredUrl }).then(
function (data){
complete(data);
}, function (err){
fail(err);
});
});
}
MyApp.Services.Movies.getFeaturedData().then(
function (data){
//do whatever you want with the data
}, function (err){
// handle errors here
});
It is easier for me to see that getFeaturedData returns a promise. In your case it is clear since it is well known that WinJS.xhr returns a promise, but it will not always be that easy to 'see' right away.

Categories