This code snippet worked in 1.7.2 with both success/error callbacks as well as promises style callbacks. With 1.8.2 the success/error callbacks still work but the promises do not. My hunch is that the return dfd.promise(jqXHR); line is the problem but im not certain.
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
// Don't infinitely recurse
originalOptions._retry = isNaN(originalOptions._retry)
? Common.auth.maxExpiredAuthorizationRetries
: originalOptions._retry - 1;
// set up to date authorization header with every request
jqXHR.setRequestHeader("Authorization", Common.auth.getAuthorizationHeader());
// save the original error callback for later
if (originalOptions.error)
originalOptions._error = originalOptions.error;
// overwrite *current request* error callback
options.error = $.noop();
// setup our own deferred object to also support promises that are only invoked
// once all of the retry attempts have been exhausted
var dfd = $.Deferred();
jqXHR.done(dfd.resolve);
// if the request fails, do something else yet still resolve
jqXHR.fail(function () {
var args = Array.prototype.slice.call(arguments);
if (jqXHR.status === 401 && originalOptions._retry > 0) {
// refresh the oauth credentials for the next attempt(s)
// (will be stored and returned by Common.auth.getAuthorizationHeader())
Common.auth.handleUnauthorized();
// retry with our modified
$.ajax(originalOptions).then(dfd.resolve, dfd.reject);
} else {
// add our _error callback to our promise object
if (originalOptions._error)
dfd.fail(originalOptions._error);
dfd.rejectWith(jqXHR, args);
}
});
// NOW override the jqXHR's promise functions with our deferred
return dfd.promise(jqXHR);
});
Update: Here is my ajax request that fails:
$.ajax({
url: someFunctionToGetUrl(),
// works
//success: callback,
//error: ajaxErrorHandler
}).then(
[callback],
[errorback, ajaxErrorHandler]
);
};
Edit: This is a documentation bug, but the behavior is by design. The api changed such that deferred.then now behaves like deferred.pipe and no longer allows arrays to be passed in, but the documentation hasn't been updated to reflect that.
Related bugs:
github: deferred.then now behaves like deferred.pipe but the documentation is stale
jQuery Bug Tracker: Ticket #12765: deferred.then([SuccessArrayFuncs],failFunc) does not work as described
The workaround I describe at the end of my original answer below still applies.
Original answer:
It looks like a jQuery bug to me. If you pass in a single function reference as the first argument, it works, but not if you pass in an array of functions:
http://jsfiddle.net/tunDH/
But, the documentation says an array of functions is just fine:
doneCallbacks A function, or array of functions, called when the Deferred is resolved.
And, you are right. It does work with jQuery 1.7: http://jsfiddle.net/tunDH/1/
A workaround would be to wrap all your function calls inside a single function, instead of inside an array:
$.ajax({
url: someFunctionToGetUrl(),
// works
//success: callback,
//error: ajaxErrorHandler
}).then(
function(){
callback1.apply(this, arguments);
callback2.apply(this, arguments);
},
[errorback, ajaxErrorHandler]
);
http://jsfiddle.net/tunDH/2/
You'll probably need to do the same thing with the error callbacks, but I didn't test that.
Related
The below non-functional example should explain what I'm trying to do, I just don't understand the pattern I need to use to accomplish it. I tried googling to understand polling and deferred, but I couldn't find anything I could understand.
I have a function which polls an API, and I want to wait for that polling to return an expected result (waiting for the endpoint to indicate something has changed) before continuing with my main function. What am I doing wrong?
Edit: I should add that what SEEMS to go wrong with the code below is that even though deferred.resolve() eventually gets called, it seems it isn't the same deferred that got returned, so the when never gets activated in main(). I'm guessing it has to do with the timeout, meaning deferred get clobbered on the first repeat. That is my assumption, anyways.
function pollAPI() {
var deferred = $.Deferred();
$.ajax({
url: url,
contentType: 'application/JSON',
method: 'GET'
}).done(function(data){
if (!desiredResult) {
setTimeout(function() {
pollAPI();
}, 1000);
} else {
deferred.resolve();
}
}).error(deferred.reject());
return deferred.promise();
}
function main() {
$.when(pollAPI()).then(function() {
// do something now that the API has returned the expected result
});
You can use chaining of subsequent calls to the pollAPI() function to create a single promise that has others chained onto it. That would work like this:
// utility function to create a promise that is resolved after a delay
$.promiseDelay = function(t) {
return $.Deferred(function(def) {
setTimeout(def.resolve, t);
}).promise();
}
function pollAPI() {
return $.ajax({
url: url,
contentType: 'application/JSON',
method: 'GET'
}).then(function(data) {
// some logic here to test if we have desired result
if (!desiredResult) {
// chain the next promise onto it after a delay
return $.promiseDelay(1000).then(pollAPI);
} else {
// return resolved value
return someValue;
}
});
}
function main() {
pollAPI().then(function(result) {
// got desired result here
}, function(err) {
// ended with an error here
});
}
This has the following benefits:
No unnecessary promise is created to try to surround the ajax call that already has a promise. This avoids one of the common promise anti-patterns.
Subsequent calls to the API just chain to the original promise.
There is no need to use $.when() when you just have a single promise. You can just use .then() directly on it.
All errors automatically percolate back to the original promise.
This uses the ES6-standard .then() (which actually becomes more legitimately standard in jQuery 3.x - though it works in jQuery 1.x and 2.x with its own non-standard quirks) which makes this logic more compatible with other promise-producing async functions.
Also, a number of other retry ideas here: Promise Retry Design Patterns
I have the following:
if(typeof searchDOM === "undefined"){
dojo.xhrPut({
url: addrPath + "/ContServlet?mod=1&act=23",
handleAs: "xml",
timeout: xhrTimeout(TIMEOUT_LRG),
load: function(dom, ioArgs){
if(dom instanceof Error){
console.error(dom);
} else{
cacheDOM = dom;
}
},
error: function(response, ioArgs){
xhrError(ioArgs, methodName);
}
});
}
The variable cacheDOM is a global variable declared(but not initialised) elsewhere in another script. It is an xml document containing the entire dom, and it is passed into:
the problem is, cacheDOM is undefined when it gets to fetchXml, and this is causing problems for methods like selectNode further down the function.
I haven't had much exposure to xhr calls, or things such as deferreds or promises, but I think that they may be able to help with this. How do i code this so that the rest of the method that this block is in will only execute if cacheDOM has been assigned the value of dom? Or if deferreds are the answer, how would i incorporate them into this code? The version of dojo i am using is 1.7.8
Well, the problem is indeed that you're using an XHR request which is asynchronous. So, the fetchXml function has to wait until that request is completed.
There are several ways to do this, you could call the fetchXml function from within the load function of dojo.xhrPut, but this is not really a good solution when your project grows because it creates a lot of dependencies on each other.
So, some smart people created an API for resolving asynchronous requests, called promises/deferreds.
So, what you have to do is assigning a new deferred to cacheDOM, for example:
require(["dojo/_base/Deferred"], function(Deferred) {
cacheDOM = new Defered();
});
Then, in the fetchXml() code you have to change your code a bit to do this:
function fetchXml() {
cacheDOM.then(function(realCache) {
console.log(realCache);
});
}
So in stead of directly using cacheDOM you have to wait for it using cacheDOM.then(). It will fire a callback when it's resolved, and the data will be available in realCache.
An alternative would be to call the entire fetchXml function when the XHR request has fired:
cacheDOM.then(fetchXml);
function fetchXml(cacheDOM) {
// Work with cacheDOM
}
This might take less work and less alteration to the fetchXml function depending on how much it relies on cacheDOM.
Then finally, inside your dojo.xhrPut you will have to do the following:
cacheDOM.resolve("My data");
Where "My data" would be the actual data which you would put inside cacheDOM.
DEMO: http://jsfiddle.net/rf20s9hb/1/
I concede that, despite hours of reading and attempting, I am fundamentally unable to grasp something about Deferred promises and asynchrony in general.
The goal on my end is real, real simple: send some data to the server, and react to the contents of the response conditionally.
The response will always be a JSON object with save and error keys:
{ "save": true, "error":false}
// or
{ "save" : false,
"error" : "The server has run off again; authorities have been notifed."}
I have tried dozens and dozens of variations from the jQuery API, from other stackexchange answers, from tutorials, etc.. The examples all seem concerned with local asynchronous activity. When I need is some ability to be made aware when the AJAX request has either finished and returned a response I can inspect and make decisions about, or else to know that it's failed. Below, I've used comments to explain what I think is happening so someone can show me where I'm failing.
I know this is a repost; I am, apprently, worse than on average at grasping this.
var postData = {"id":7, "answer":"Ever since I went to Disneyland..."};
/* when(), as I understand it, should fire an event to be
responded to by then() when it's contents have run their course */
var result = $.when(
/* here I believe I'm supposed to assert what must complete
before the when() event has fired and before any chained
functions are subsequently called */
/* this should return a jqXHR object to then(), which is,
I'd thought, a queue of functions to call, in order,
UPON COMPLETION of the asynchronous bit */
$.post("my/restful/url", postData))
.then( function() {
/* since "this" is the jqXHR object generated in the $.post()
call above, and since it's supposed to be completed by now,
it's data key should be populated by the server's response—right? */
return this.data;
});
// alas, it isn't
console.log(result.data);
// >> undefined
Most examples I can find discuss a timeout function; but this seems, as I understand, to be a failsafe put in place to arbitrarily decide when the asynchronous part is said to have failed, rather than a means of stalling for time so the request can complete. Indeed, if all we can do is just wait it out, how's that any different from a synchronous request?
I'll even take links to a new read-mes, tutorials, etc. if they cover the material in a different way, use something other than modified examples from the jQuery API, or otherwise help this drooling idiot through the asynchronous mirk; here's where I've been reading to date:
jQuery API: Deferred
JQuery Fundamentals
jQuery Deferreds promises asynchronous bliss (blog)
StackOverflow: timeout for function (jQuery)
Update
This is in response to #Kevin B below:
I tried this:
var moduleA = {
var moduleB = {
postData: {"id":7, "answer":"Ever since I went to Disneyland..."};
save: function() {
return $.post("path/to/service", postData, null, "JSON");
}
};
var result = this.moduleB.save();
result.done(function(resp) {
if (resp.saved == true) {
// never reached before completion
console.log("yahoo");
} else {
console.log("Error: " + resp.error);
// >> undefined
}
});
}
You are over-complicating your code. You cannot get the data to outside of the callback, no matter how many deferred/promises you create/use (your sample creates 3 different deferred objects!)
Use the done callback.
var postData = {"id":7, "answer":"Ever since I went to Disneyland..."};
$.post("my/restful/url", postData).done(function (result) {
console.log(result.save, result.error);
});
You seem to have a misunderstanding of both asynchronous requests, the Promise pattern, and Javascripts mechanism of passing functions as an argument.
To understand what's really happening in your code I suggest you use a debugger and set some breakpoints in the code. Or, alternatively, add some console.logs in your code. This way you can see the flow of the program and might understand it better. Also be sure to log the arguments of the function you pass as an argument in the then()-method, so you understand what is passed.
ok you got it half right. the problem is that when you execute the console.log the promised is not yet fulfilled the asynchronous nature of the promises allows the code to execute before that ajax operation is done. also result is a deferred not a value, you need to handle your promised with .done instead of .then if you wish to return a value otherwise you'll continue passing promises.
so that said
var result={};
$.when(
$.post("my/restful/url", postData))
.done( function(data) {
result.data=data;
});
// here result is an object and data is a undefined since the promised has no yet been resolve.
console.log(result.data);
I'm having some issues with the chaining of deferred objects. So I think that Im missing some understanding. My code is something like follow:
var AJAX_FUNC_CREATE_ALIAS = function(){
return $.when(ajax_call()).then(function(response){
// DO something with the response I get to compose an object
return composed_response;
});
}
var name = 'Alejandro',
alias = 'Ali';
$.when(AJAX_FUNC_CREATE_NAME)).then(function(response, status, jqXHR){
return AJAX_FUNC_CREATE_ALIAS(name); // <-- Wait correctly
},function(jqXHR, status, errorThrown){
return default_response;
}).done(function(artist_response){
var promise = AJAX_FUNC_CREATE_ALIAS(alias); // <----- Problematic one
console.log(promise.state()); // It shows pending state
return promise;
}).done(function(alias_response){
$.publish(channel, [alias_response])
});
The execution goes like this:
The AJAX_FUNC_CREATE_NAME function start execution. When it finishes it goes to the callback defined inside the then().
It executes the AJAX_FUNC_CREATE_ALIAS(name) function. The .done() method is not executed until the AJAX_FUNC_CREATE_ALIAS(name) has finished.
It start executing AJAX_FUNC_CREATE_ALIAS(alias); In here it does not wait to get answer from the server. It goes straight to the $.publish(....)
Why?
Update: I have added some code for checking the answer I get from the problematic line. The promise I get back seems to have 'pending state'. From the jquery documentation .done() methods : "Add handlers to be called when the Deferred object is resolved." [http://api.jquery.com/deferred.done/] . If the state is pending... why is the done() method getting it?
then returns a new promise,done doesnt.
If you want to chain tasks use then.that's what you did for the second promise,it only makes sense to do the same for the next one.
It's written right here:
http://api.jquery.com/deferred.then
I have a function that looks like this
this.getToken = function() {
if (token === null) {
token = getAccessTokenAsync("username", "password");
lastTokenTime = getTokenExpiryAsync();
}
}
this function will call getAccessTokenAsync which will make a request to my web server with xhr. This looks like this:
getAccessTokenAsync = function (username, password) {
var serializedData = {
username: username, password: password,
};
return new WinJS.Promise(function (complete) {
WinJS.xhr({
type: "post",
url: "http://127.0.0.1:8080/authenticate/login",
responseType: "json",
data: JSON.stringify(serializedData)
}).done(
function complete(result){
return JSON.parse(result.responseText);
}
);
})
}
I would expect token to now store a promise inside of it. Which when we then call .done() or .next() will have the json object which got returned by the server. However when I call getTokenExpiryAsync() something else happens.
getTokenExpiryAsync = function () {
if (token === null) {
return new Date();
}
token.then(
function complete(result){
console.log(result);
},
function onerror(error) {
console.log(error);
},
function onprogress(data) {
});
}
instead it doesn't seem to call any of the functions in the .then() it just skips right past it!. Strict mode is enabled so my token variable does have a promise inside of it. Otherwise it would of errored as it wouldn't be able to find the .done() method?
my question is why is this happerning and how can I get the expected behaviour that I want (token having a promise stored in it from getAccessTokenAsync which I can access in other methods).
In your code, it's unnecessary to create a new WinJS.Promise because WinJS.xhr().then will return the promise you want. To give the background, there are two ways to attach completed handlers to a promise: .then and .done. Both take the same arguments but differ in return value. .done returns undefined, as it's meant to be used at the very end of a promise chain.
.then, on the other hand, returns a promise that's fulfilled when the completed (or error handler) returns, and the fulfillment value is the value returned from the completed handler (or the error handler).
(By the way, I've written a bunch more on promises to clarify issues like this. A short version can be found All about promises (Windows Dev Blog); a more complete version can be found in Appendix A, "Demystifying Promises," of my free ebook, Programming Windows Store Apps in HTML, CSS, and JavaScript, Second Edition, which is in it's second preview right now.)
When writing any async function of your own, the best pattern to use when calling other existing async functions (like WinJS.xhr) is to return a promise from its .then. So in your case, you want getAccessTokenAsync to look like this:
getAccessTokenAsync = function (username, password) {
var serializedData = {
username: username, password: password,
};
return WinJS.xhr({
type: "post",
url: "http://127.0.0.1:8080/authenticate/login",
responseType: "json",
data: JSON.stringify(serializedData)
}).then(
function complete(result){
return JSON.parse(result.responseText);
}
);
})
}
This will return a promise, which you assign to token, whose fulfillment value will be the result from JSON.parse(result.responseText).
Let me explain now why your original use of new WinJS.Promise is incorrect--it's a common misunderstanding that my other writings clarity. The function argument that you give to the constructor here itself receives three arguments. Each argument is another function, each of which I call a "dispatcher," and you get one for complete, error, and progress. The body of your code inside the promise much call these dispatchers upon the appropriate events.
Those dispatchers, in turn, then call the completed, error, and progress handlers for any functions subscribed through the promise's .then or .done. Calling these dispatchers, in other words, is the only way that you actually triggers calls to those handlers.
Now in your original code you never actually call any of these. You've kept it simple by having the WinJS.Promise constructor just pay attention to the completed dispatcher. However, when your WinJS.xhr call completed, you're not calling this dispatcher. Part of the confusion is that you have an argument called complete, and then name your completed handler for WinJS.xhr().done "complete" as well. If you set breakpoints on the last JSON.parse call, it should be getting hit, but the value your returning just gets swallowed because it's never passed to the complete dispatcher.
To correct this, you'd want your original code to look like this:
return new WinJS.Promise(function (completeDispatch) { //Name the dispatcher for clarity
WinJS.xhr({
type: "post",
url: "http://127.0.0.1:8080/authenticate/login",
responseType: "json",
data: JSON.stringify(serializedData)
}).done(
function (result) { //Keep this anonymous for clarity
completeDispatch(JSON.parse(result.responseText));
}
);
})
This should work as well. However, it's still easiest to just return the promise from WinJS.xhr().then() as I originally noted, because you don't need another promise wrapper at all.
With either of these changes, you should now see a call to the completed handler within getTokenExpiryAsync.
Let's talk about other parts of your code now. First of all, token will always be set to a promise even if there's an error condition, so you'll never see a null case inside getTokenExpiryAsync. Secondly, if you use the new WinJS.Promise code as above, you'll never see error or progress cases, because you're never calling the errorDispatcher or progressDispatcher. This is another good reason to just use the return from WinJS.xhr().then() instead.
So you'll need to think through your error handling a little more closely here. What, exactly, are the cases where you want to call new Date() for an expiry? Do you do this when the xhr call fails, or when the response from a successful call returns empty?
One way to handle errors is to use the new WinJS.Promise variant above, with WinJS.xhr().done(), where you subscribe an error handler to .done. In that error handler you then determine whether you want to propagate the error, or whether you want to still fulfill the wrapper promise with a new Date, by calling completeDispather(new Date());. For other errors, you'd call the errorDispatcher. (Note that all this assumes that a successful xhr response contains the same format of data as new Date(), otherwise you're intermixing data values and would want to parse the date out of the response instead of just returning the whole response.)
return new WinJS.Promise(function (completeDispatch) { //Name the dispatcher for clarity
WinJS.xhr({
type: "post",
url: "http://127.0.0.1:8080/authenticate/login",
responseType: "json",
data: JSON.stringify(serializedData)
}).done(
function (result) { //Keep this anonymous for clarity
completeDispatch(JSON.parse(result.responseText));
},
function (e) {
completeDispatch(new Date()); //Turns an xhr error into success with a default.
}
);
})
What I've just described is really a good way to catch errors in your core operation and then inject a default value, which is what I believe you intend.
If you use the return value from WinJS.xhr().then(), on the other hand (the first code variant), then you need to put more of this logic inside getTokenExpiryAsync. (By the way, this code, as you show it, is synchronous, and one code path returns a new Date and the other return undefined, so it's not quite what you want.)
Now because token itself is a promise, this getTokenExpiryAsync does need to be async itself, and therefore needs to also return a promise for the expiry. Here's how you'd write that:
function getTokenExpiryAsync (token) { //I'd pass token as an argument here
return token.then(
function complete(result) {
return result; //Or parse the date from the original response.
},
function error(e) {
return new Date();
}
);
}
And then in your calling code you'll need to say:
getTokenExpiryAsync(token).then(function (expiry) {
lastTokenTime = expiry;
}
Again we're making use of the return value of then being another promise whose fulfillment value is what's returned from the completed or error methods. If token is in an error state (WinJS.xhr failed), then your call to .then will invoke the error handler, where you then return the desired default. Otherwise you return whatever expiry you want from the response. Either way, you get a date from this promise from .then in the original calling code.
I know this can be a bit confusing, but it's the nature of the Promises/A specification and async coding and not WinJS in particular.
Hope all this is worth your bounty. :)
It looks like your then functions aren't called because you aren't calling your promise function's complete callback. Also, your WinJS.xhr is a promise so you can just return that without wrapping it in another promise.