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
Related
I'm working with a string of XMLHttpRequests that each depend on the one prior to it. Psuedocode:
xhr1.open('GET', 'http://foo.com');
xhr1.onload = function(e){
xhr2.open('POST', xhr1.response.url)
xhr2.onload = function(e){
xhr3.open('GET', xhr2.response.url2);
xhr3.onload = function(e){
console.log('hooray! you have data from the 3rd URL!');
}
xhr3.send();
}
xhr2.send();
}
xhr1.send();
Is this the kind of situation where using promises would be a good idea to avoid all the callback muck?
Yes. If you return a promise in a then, the next chained then listens for that promise instead of resolving from the original promise. Given that ajaxCall returns a promise, your code will then look like:
ajaxCall(1)
.then(function(result1){
return ajaxCall(2);
})
.then(function(result2){
return ajaxCall(3);
})
.then(function(result3){
// all done
});
// Sample AJAX call
function ajaxCall(){
return new Promise(function(resolve, reject){
// xhr code. call resolve/reject with accordingly
// args passed into resolve/reject will be passed as result in then
});
}
Yes, definitively. Assuming a helper function like those from How do I promisify native XHR?, your code could be transformed into
makeRequest('GET', 'http://foo.com').then(function(response1) {
return makeRequest('POST', response1.url);
}).then(function(response2) {
return makeRequest('GET', response2.url2);
}).then(function(response3) {
console.log('hooray! you have data from the 3rd URL!');
});
Still callbacks of course, but no more nesting required. Also you'd have simple error handling, and the code looks much cleaner (partially contributed to by the non-promise-related fact of abstracting XHR in its own function).
I am trying to create the following promise syntaxes:
I want the secondary then to be called after the post function, how can I do that?
randomBytes.then(function() {
return requestPromise.post(....);
}).then(function() {
// I want this part to be called after the POST
});
The fact this already works (as the comment said - assuming .post returns a promise) is pretty much the point of then - it allows you to wait for stuff.
If you return a promise from then, the chain will assume its state and thus will only call the following segments after it has completed.
randomBytes.then(function() {
return requestPromise.post(....);
}).then(function() {
// WILL ALWAYS BE CALLED AFTER THE POST
});
it should work.
and you can add a 'then' to get the post value;
randomBytes.then(function() {
return requestPromise.post(....).then((value,...)=>{return value;});
}).then(function(value) {
//console.log(value);
});
I'm really hoping that there's something dumb that I'm doing, but I can't seem to find it.
I'm trying to use Ember.RSVP.all in the middle of a chain of promises. The example I have is much simpler than my use, but it demonstrates the issue. In the middle of a chain of promises, I have a set of promises that all need to resolve before the chain can continue - exactly what I understand RSVP.all to be for.
Unfortunately, when I return the RSVP.all object, the next promise in the chain runs immediately, without waiting for the promises passed to all().
I've set up a js fiddle to demonstrate in the best way that I can think of:
http://jsfiddle.net/3a9arbht/3/
Notice that First and Second both resolve at almost exactly the same time, when Second should be after the 1s promise comes back. Third and fourth follow as expected.
Fiddle code looks like this:
function delayAjax(delay) {
return Ember.$.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
});
}
delayAjax(1).then(function() {
Ember.$('#first').addClass('red');
var proms = [delayAjax(1), delayAjax(1)];
return Ember.RSVP.all(proms)
}).then(function() {
Ember.$('#second').addClass('red');
return delayAjax(1);
}).then(function() {
Ember.$('#third').addClass('red');
return delayAjax(1);
}).then(function() {
Ember.$('#fourth').addClass('red');
});
Answering based on a response to another question. It appears that while the $.ajax response is indeed "thenable", it is a jQuery deferred object, not a Promise. It's not clear to me why they don't play well together, but the solution is simply to convert the ajax call to a promise:
function delayAjax(delay) {
return Promise.resolve($.ajax({
url: '/echo/json/',
data: {
json: '',
delay: delay,
}
}));
}
With a working fiddle: http://jsfiddle.net/evilbuck/vqut9zy2/3/
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.
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.