I am just trying to demystify how the method $.Deferred works in jQuery. I know that its mostly used for AJAX stuff, but I would also like to use this method for non-AJAX stuff. I was reading the jQuery documentation on this method and came across the following piece of code:
$.fn.bindOnce = function( event, callback ) {
var element = $( this[ 0 ] ),
defer = element.data( "bind_once_defer_" + event );
if ( !defer ) {
defer = $.Deferred();
function deferCallback() {
element.unbind( event, deferCallback );
defer.resolveWith( this, arguments );
}
element.bind( event, deferCallback )
element.data( "bind_once_defer_" + event , defer );
}
return defer.done( callback ).promise();
// what is this piece of code really doing and why is it necessary ?
};
... now if you walk through the code line by line its pretty easy to under whats going on.
The jQuery documentation tells us whats happening line by line , like so :
Check if the element already has a deferred attached for the given event
if not, create it and make it so it is resolved when the event is fired the first time around
then attach the given callback to the deferred and return the promise.
the difficulty I have and the line of however that I cannot understand is below :
return defer.done( callback ).promise();
I fail to understand the purpose of this line of code and why it is useful and what exactly is the promise method doing in this scenario ?
Can anyone explain?
promise() creates a promise object for a deferred. The promise object exposes a subset of the methods available on the deferred; which let clients register event handlers to the various events, but not modify the deferred itself.
From the jQuery docs:
The promise object provides a subset of the methods of the Deferred object (then, done, fail, always, pipe, progress, state and promise) to prevent users from changing the state of the Deferred.
Therefore,
return defer.done( callback ).promise();
... is adding the callback function to be executed when the deferred (defer) resolves, then returns the corresponding promise for the deferred.
You may find the following question useful; What are the differences between Deferred, Promise and Future in JavaScript?
Related
Sometimes I see more than two functions separated by comma, inside of promise then() in AngularJs. Could anyone here help to explain what the structure mean?
e.g.
then(function(response){..}, function(response){..}, function(response){..}, function(response){..});
I understand that, if there are two functions inside of then(). The first function will run if it fulfill the promise, otherwise the second will run if there is any error occurs. This structure doesn't look like chained promise either...
Thank you very much for any help here :-)
Well:
The first function is the fulfillment handler, it will execute when the promise resolves to a value (normally).
The second function is the rejection handler, it will execute when the promise resolves with an error by rejecting (or throwing an exception in a handler).
The third function is for progress, this is a nonstandard and deprecated feature that will never be a part of ECMAScript promises - it is best to avoid it altogether since it does not compose well. It is also not supported in Angular's new $q API.
Anything past the third function passed in is ignored by the handler and will have no effect.
Here is an example for all three being called:
var good = $q.when(3);
good.then(x => console.log("Successful"));
var bad = $q.reject(Error("Bad")); // always reject with errors
bad.then(null, x => console.log("Failed"));
var d = $q.defer(); // don't do this like... ever
d.promise.then(null, null, x => console.log("Progressed"));
d.notify("event notification"); // never use this in real code ;)
This question already has answers here:
$.Deferred: How to detect when every promise has been executed
(3 answers)
Closed 7 years ago.
I've looked through many similar questions but the proposed solutions do not work in all cases as expected. The following code works fine when all ajax calls are successfully done but if any of ajax calls happens to fail, then onComplete is immediately called:
var deferredArray = $('.preload').map(function() {
return $.get(this.href)
});
$.when.apply($, deferredArray).then(onComplete, onComplete);
So there can be two cases:
all deferred calls are successful, then onComplete is called afterwards - works fine;
some deferred call fails (returns HTTP 400 Bad request), then onComplete is called immediately not waiting for other deferred calls.
The second case represents my problem. It should always wait for all calls to complete regardless of the status before calling onComplete.
I use jQuery 1.7.1 as it's built into the framework. If the issue is due to the version, I can upgrade but I'd prefer to keep the current version.
You could try ajaxStop(). Allbeit not the most elegant solution.
$( document ).ajaxStop(function() {
// do some stuff now that all the ajax stuff has stopped
})
Also note:
If $.ajax() or $.ajaxSetup() is called with the global option set to false, the .ajaxStop() method will not fire.
I'm not sure if this outside of the scope of the types of solutions you're looking for, but I've recently been taking advantage of bluebird.js to wrap all my jquery ajax calls.
For example:
Promise.resolve($.get("http://www.google.com")).then(function() {});
What this allows me to do is then perform a Promise.all which will then wait for all the ajax calls to be complete. Each individual promise can pass or fail, but .then (or .spread) on the .all will only be called if they all succeed.
var aPass = function(){
return Promise.resolve($.get("/echo/json/"))
};
var aFail = function(){
return Promise.resolve($.get("/echo/jsonFake/")) // this will fail
};
var allPass = [aPass(), aPass(), aPass()];
var oneFails = [aPass(), aPass(), aFail()];
Promise.all(allPass).spread(function(a,b,c){
console.log("All Done!");
}).catch(function(){
console.log("NEVER FAILS!");
});;
Promise.all(oneFails).spread(function(a,b,c){
console.log("never goes in here");
}).catch(function(){
console.log("FAILED");
});
Here is a jsfiddle demonstrating this: http://jsfiddle.net/ys598t4s/2/
I am writing an asynchronous javascript function that will be called by consumers to get certain data. Following is the simple implementation that I wrote initially (error handing and other stuff removed for clarity).
function getData(callback){
if (data is available as a JavaScript object){
callback(data);
}else{
getAsyncData(function(data){
//some transformations on data
callback(data);
});
}
}
What is important to note is that getData can return data quickly if data is already available as a JavaScript object.
I want to replace this implementation with the one that returns a promise object to the caller. This fiddle shows sample implementation - http://fiddle.jshell.net/ZjUg3/44/
The question - Since getData can return quickly, can there be a possiblity where getData is resolving the promise even before caller has established handler chain using then method? Just to simulate this, in the fiddle if i call then method inside setTimeout function (with zero delay), callback doesn't get called. If i call the then method outside of the setTimeout function, callback gets called. I am not sure if this is even a valid concern or valid usecase. I am quite new to angularjs development and would appreciate your views :)
If you want getData() to return a $q promise instead of using a callback, I'd do the following refactor using $q.when() and usual $q.resolve():
function getData()
{
if (data is available as a JavaScript object) {
return $q.when(data); // resolves immediately
} else {
var q = $q.defer();
getAsyncData(function(data){
//some transformations on data
q.resolve(data);
});
return q.promise;
}
}
No, a significant and important part of being a promise is that it doesn't matter when you attach the handler. Even if you create a promise now and resolve it immediately, then keep your computer running for the next 50 years, then attach a handler it will still fire.
All of this does assume that there isn't a bug/corner case in angularjs's promise implementation. If it doesn't work, it's a bug though.
If you ever need to know anything about how promises work, you can always refer to the Promises/A+ spec which angular adheers to. As a spec, it's one of the simplest and easiest to understand that I've come across (although I should mention that I've been involved in the spec for quite a while now).
I wanted an easy way to add pauses into some stuff that I chain together with promises.
I thought it would be nice to include a "wait" method so I could write stuff like:
var promise = new WinJS.Promise(function(complete){
whatever()
}).wait(1000).then(function(){
whatever2();
}).wait(500).done(function(){
alldone();
}
So to do this I added a wait method to the Promise class like so:
if (WinJS.Promise.prototype.wait == null) {
WinJS.Promise.prototype.wait = function (milliseconds) {
var promise = new WinJS.Promise(function (complete) {
setTimeout(complete, milliseconds);
});
return promise;
}
}
It seemed to be working, but I noticed that if I use a "then", the object I get back from it, while the documentation says is a WinJS.Promise, won't have a wait function. The promises I create all DO have the wait function, but calling .then() on a promise will cause the subsequent .wait to fail, so...
promise.wait(300).then().done();
is no problem but:
promise.then().wait(300).done();
will error out saying that there is no wait method on the Promise returned from then().
Can anyone explain what I'm doing wrong?
There are two reasons why your code doesn't work.
The first is that Microsoft has used their own approach to creating object instances, which they do through the WinJS.Class namespace - this means that the prototype you are altering with the addition of your wait function doesn't ever get applied to the WinJS.Promise objects you are consuming in your code.
The second problem is that you are targeting the wrong object - the WinJS.Promise.then method calls WinJS.Promise.as which returns a CompletePromise object - so even if you could make your wait function stick, it would be in the wrong place. The CompletePromise definition is not in the public scope, so you'd have to do a lot of hacking to be able make the change you want.
There is a solution, but you have to use the WinJS.Promise.timeout method. You can't use this inline, which means that to get the effect you want, you will need some slightly awkward code, as follows;
var promise = new WinJS.Promise(function (complete) {
whatever();
complete();
}).then(function () {
return WinJS.Promise.timeout(1000);
}).then(whatever2).then(function() {
return WinJS.Promise.timeout(500);
}).then(alldone);
This not a direct answer to your question. Adding a wait() method to Promise's prototype should indeed work, unless then() returns an object that looks like a Promise, quacks like a Promise, but is not actually a Promise.
That said, you do not have to implement a wait() method in the first place, because Promise already exposes a timeout() method that does the same thing (and more, actually). You're looking for its single-argument form:
var promise = new WinJS.Promise(function(complete) {
whatever();
}).timeout(1000).then(function() {
whatever2();
}).timeout(500).done(function() {
alldone();
});
How can I cancel a promise without removing the element from the DOM?
fiddle
I ran this code:
$("#box")
.delay(2000)
.show("slow")
.delay(2000)
.promise()
.then(function(){log("Done");});
After this, is there a way to cancel the promise? Both clearQueue() and stop(true) didn't work, because it's not an animation that I'm trying to cancel. I saw that remove() should do it ... but I only want to stop the promise, not remove the entire element.
Good news. Since yesterday you can cancel your promise.
I published the new version of my small plugin jquery-timing that provides two methods amongst many others called .wait() and .unwait().
var deferred = $("#box").delay(2000).show("slow").delay(2000).promise();
$.wait(deferred, function(){ log("Done"); });
If you then want to unregister the callback:
$.unwait();
These static versions of wait and unwait also support an optional group name to not cancel any handler but only a specific set.
Besides that you can do a lot more smart stuff like:
$('#box').wait(deferred).addClass('ready');
or the whole code in one chain, without unwait option:
$("#box").delay(2000).show("slow")
.delay(2000).join(function(){log("Done");})).addClass('ready');
or the same even shorter with option to cancel the two pauses:
$("#box").wait(2000).show("slow",$)
.wait(2000, function(){log("Done");})).addClass('ready');
Just see the docs, examples, and API what fits best for you.
I believe you can use $('#box').remove();
From the jQuery documentation here: http://api.jquery.com/promise/
The returned Promise is linked to a Deferred object stored on the .data() for an element. Since the .remove() method removes the element's data as well as the element itself, it will prevent any of the element's unresolved Promises from resolving. If it is necessary to remove an element from the DOM before its Promise is resolved, use .detach() instead and follow with .removeData() after resolution."
I don't suppose you'd want something like http://jsfiddle.net/2cq8M/ ? I'm involving two promises (one just to handle the case at the end of the set of animations, the other to resolve or reject as needed).
You want to use a deferred in this case instead of a promise, however, you can use the promise of the animation to resolve the deferred.
http://jsfiddle.net/LG9eZ/9/
var stopDone = true;
function log(msg) {
$(".debug").append(new Date() + " - " + msg + "<br/>");
}
log("Starting");
var boxAnimation = new $.Deferred();
boxAnimation.done(function() {
log("Done");
});
boxAnimation.fail(function() {
log("Stopped");
});
$("#box").delay(2000).show("slow").delay(2000).promise().then(function() {
boxAnimation.resolve(); // when all the animations are done, resolve the deferred.
});
if (stopDone)
{
boxAnimation.reject();
}
As a side note, deferreds can only rejected or resolved once. Once they are rejected or resolved, you cannot change their state.