NOT AN "ANTIPATERN": This is not common "antipattern" that can be solved simply by returning nested Deferred object directly... This excerpt is heavy simplification of nested lazily loaded code that will return Deferred in near future so it is impossible to return not-yet-existing Deferreds directly from the original method.
In addition to that the Deferred returned from original (top level) method depends on multiple Deferreds created in near future where only "rejection" is relayed immediately back...
I found myself writing lately things like this too often:
My.prototype.init=function() {
var $dfd = $.Deferred();
this.initSomethingElse() // returns Deferred
.done(function() {
$dfd.resolve();
})
.fail(function() {
$dfd.reject();
});
return $dfd;
}
I wrote short function to add .link() method on Deferred to simplify it as follows:
My.prototype.init=function() {
var $dfd = $.Deferred();
this.initSomethingElse() // returns $.Deferred(addLinkMethodFce)
.link($dfd);
return $dfd;
}
But I am thinking that it is probably way too common so somebody else probably thought of it and I might have missed some out-of-the-box solution in jQuery.
Question: Can jQuery chain existing Deferred object with other Deferred object in a way that if linked object is resolved/rejected current Deferred will be resolved/rejected in the same way?
Simply something like dfd1Promise = $.when(dfd2)
except without a need to create new Promise and rather simply "link" existing objects together without creating new objects dfd1.link(dfd2)
.
Thanks.
EDIT#1: As I see many answers to questions I haven't ask - like "Should I use Deferred?" I'd like to clarify the situation.
I am having my asynchronously loaded code (My.prototype.init
does load asynchronous scripts and data which makes it return Deferred to a caller).
And that code also depends on asynchronous third-party code - API I cannot modify - initSomethingElse()
that will be resolved in a near future as well and returns Deferred.
The point is that if that code gets rejected my code must get rejected. And I was hoping for some magic standard sugar syntax for repeating $theirDfd.reject(function() {$myDfd.reject.call(...);});
that is all.
Simply is there a standard way of doing my $myDfd.link($theirDfd);
?
I just heavily simplified the situation to express it on few lines so don't be tricked into thinking that I might not need Deferred or whatever else I didn't ask...
EDIT#2: To clarify it even further what .link() does, here is my helper code I currently use:
/**
* Adds some syntax sugar methods to Deferred object.
*
* Deferred.link(dfd, ...) - if this object resolves/rejects/progresses it will resolve/reject/progress linked object as well
* Deferred.linkResolve(dfd, ...) - if this object resolves it will resolve linked object as well
* Deferred.linkReject(dfd, ...) - if this object rejects it will reject linked object as well
* Deferred.linkProgress(dfd, ...) - if this object progresss it will progress linked object as well
*
* Methods can be appended to Deferred object by two ways:
*
* $dfd = edfd($.Deferred());
* $dfd = $.Deferred(edfd);
*
* @access public
* @return {Deferred}
*/
function edfd($dfd) {
/**
* Helper used by this.link(), this.linkReject(), this.linkProgress(), this.linkResolve()
*
* @access private
* @param {Boolean} accept link this Deferred's accept call to target's accept
* @param {Boolean} reject link this Deferred's reject call to target's reject
* @param {Boolean} progress link this Deferred's progress call to target's progress
* @param {Object} targets array of Deferreds or array of arrays of Deferreds
* @return {Deferred} this (called in $dfd context)
*/
function linker(accept, reject, progress, targets) {
targets = dna.core.getOpts(targets, [['dfdList', 'object'], 'recursive']);
for (var i = 0; i < targets.dfdList.length; i++) {
var $link = targets.dfdList[i];
$dfd.then(
accept && function() {$link.resolve.apply($link, arguments);},
reject && function() {$link.reject.apply($link, arguments);},
progress && function() {$link.progress.apply($link, arguments);}
);
}
return this;
}
/**
* If link this Deferred's rejection/resolution/progress to all linked Deferreds.
*
* @access public
* @param {...Deferred} dfd jQuery Deferred objects or arrays of Deferred objects.
* @return {Deferred} this object
*/
$dfd.link = function() {
return linker(true, true, true, arguments);
};
/**
* If this Deferred is resolved then resolve also linked Deferreds.
*
* @access public
* @param {...Deferred} dfd jQuery Deferred objects or arrays of Deferred objects.
* @return {Deferred} this object
*/
$dfd.linkResolve = function() {
return linker(true, false, false, arguments);
};
/**
* If this Deferred gets rejected then reject also linked Deferreds.
*
* @access public
* @param {...Deferred} dfd jQuery Deferred objects or arrays of Deferred objects.
* @return {Deferred} this object
*/
$dfd.linkReject = function() {
return linker(false, true, false, arguments);
};
/**
* If this Deferred progresses then progress also linked Deferreds.
*
* @access public
* @param {...Deferred} dfd jQuery Deferred objects or arrays of Deferred objects.
* @return {Deferred} this object
*/
$dfd.linkProgress = function() {
return linker(false, false, true, arguments);
};
return $dfd;
}
EDIT#3: Just for those who are eager to see particular use. The Object's deferred is initialized like this this.$dfd = $.Deferred(edfd);
(see my solution to this problem above - function edfd(...)
/**
* Public interface as required for payment processors.
*
* @access public
* @param {Object} payData
* @return {Deferred} resolved/rejected based on payment result.
*/
Braintree.prototype.process = function(payData) {
this.cleanUp();
this.payData = payData;
this.createLayer();
this.initBraintree(payData.clientToken)
.linkReject(this.$dfd);
return this.$dfd;
};