0

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;
};
danny
  • 9
  • 2
  • 2
    This is a very common antipattern among users new-ish to async/promises in JavaScript. See the following: [What is the explicit promise construction antipattern and how do I avoid it?](http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it) – Adam Jenkins Dec 07 '16 at 17:25
  • Why is `jQuery.Deferred()` needed? – guest271314 Dec 07 '16 at 17:25
  • What would you expect .link to do exactly? as it stands i see no reason for you to need any functionality that isn't available by simply returning the existing promise. – Kevin B Dec 07 '16 at 19:31
  • @KevinB I have updated my question with complete code for my `.link()`, `.linkReject()`, `.linkResolve()`, `.linkProgress()` sugar syntax methods. My question really stands like this: is there existing thing doing the same? (Something I might miss like being able to call `$.when()` on object `$dfd.when($dfd2)` resulting in same thing... or something I might miss.) – danny Dec 08 '16 at 13:39
  • I still don't understand the purpose of all your link functions. It still just looks like the anti pattern – Kevin B Dec 08 '16 at 15:24
  • @danny_stacker What issue are you having with existing pattern at Question? – guest271314 Dec 08 '16 at 16:22

1 Answers1

-1

TL;DR

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?

I see what you're thinking and why; however, ES2015 has already solved this for you with Promise chaining. Good example: Promise.all() (MDN link) is a cool implementation of this type of functionality (see: "fail fast").

If you need, use multiple deferred's and "link" them together using jQuery.when()

var d1 = $.Deferred();
var d2 = $.Deferred();

$.when( d1, d2 ).done(function ( v1, v2 ) {
    console.log( v1 ); // "Fish"
    console.log( v2 ); // "Pizza"
});

d1.resolve( "Fish" );
d2.resolve( "Pizza" );

(source of code: https://api.jquery.com/jquery.when/)


Read this good answer on SO about your issue (and its associated link). It may not be immediately obvious why the answer helps your case, but give it thought and write some example code and you'll understand better.

As others have alluded to:

  • You want to avoid deferred's, if possible
  • Chain your promises using .then() (and .catch() at the end)
Community
  • 1
  • 1
jeremiah.trein
  • 776
  • 6
  • 6
  • There is no magical, syntactic sugar, sadly. Short answer: this way of working is using the beauty of Promises incorrectly. You could also try looking at RxJS, but that's a bigger commitment than what you seem to want. – jeremiah.trein Dec 08 '16 at 05:15
  • Can you post a clearer example of your code? It's quite difficult to give you a **precise** answer when your own question is a bit cryptic. – jeremiah.trein Dec 08 '16 at 05:16
  • Thanks for help. But I reiterate again * 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) . * I know when to use Deferreds, thanks. ;-) * .then() and .catch() results exactly in the same code with .done() and .fail() that I asked for a solution to - so this is going in circles – danny Dec 08 '16 at 13:56
  • Cool. Well, if you find the answer on your own, try to post back. Interesting scenario. – jeremiah.trein Dec 09 '16 at 04:56
  • I didn't mean to be rude. I missed your request to post clearer example. Posted. But I got an impression that there is no really magical syntactic sugar as you have already pointed out. So I think that the answer to my question is simply: **no, there is no standard syntax sugar so writing your own is not a bad thing providing that it is not used in scenarios that are considered as that known _common_ _antipattern_**. Am I right? – danny Dec 09 '16 at 12:12