Hkan
Hkan

Reputation: 3383

New $.Deferred object with the old callbacks

Please forgive me if this is a stupid question. I have been trying for hours and my brain have just stopped working.

I have such system that consists of three AJAX calls. Server response of first call usually is a 200 Success; but second and third queries are fragile because they are image uploading, and on the server side, I have so much validation rules that client's images mostly fail.

window.AjaxCall = function () {
    // to pass to $.ajax call later
    this.args = arguments;

    // xhr status
    this.status = null;

    // xhr results (jqXHR object and response)
    this.xhrResponse = {};

    this.dfr = new $.Deferred();

    // to provide an easier interface
    this.done = this.dfr.done;
    this.fail = this.dfr.fail;
    this.then = this.dfr.then;
};

AjaxCall.prototype.resetDfr = function () {
    this.dfr = new $.Deferred();
};

AjaxCall.prototype.resolve = function () {
    this.dfr.resolve(
            this.xhrResponse.result,
            this.xhrResponse.jqXHR
    );

    this.resetDfr();
};

AjaxCall.prototype.reject = function () {
    this.dfr.reject(
            this.xhrResponse.jqXHR
    );

    this.resetDfr();
};

AjaxCall.prototype.query = function () {
    var _this = this;

    // if query hasn't run yet, or didn't return success, run it again
    if (_this.status != 'OK') {
        $.ajax.apply(_this, _this.args)
                .done(function (result, textStatus, jqXHR) {
                    _this.xhrResponse.result = result;
                    _this.xhrResponse.jqXHR = jqXHR;
                    _this.resolve();
                })
                .fail(function (jqXHR) {
                    _this.xhrResponse.jqXHR = jqXHR;
                    _this.reject();
                })
                .always(function (a, b, c) {
                    var statusCode = (typeof c !== 'string'
                            ? c
                            : a).status;

                    if (statusCode == 200) {
                        _this.status = 'OK';
                    }
                });
    }

    // if query has been run successfully before, just skip to next
    else {
        _this.resolve();
    }

    return _this.dfr.promise();
};

AjaxCall class is as provided above, and I make the three consecutive calls like this:

var First = new AjaxCall('/'),
        Second = new AjaxCall('/asd'),
        Third = new AjaxCall('/qqq');

First.then(function () {
    console.log('#1 done');
}, function() {
    console.error('#1 fail');
});

Second.then(function () {
    console.log('#2 done');
}, function() {
    console.error('#2 fail');
});

Third.then(function () {
    console.log('#3 done');
}, function() {
    console.error('#3 fail');
});

var toRun = function () {
    First.query()
            .then(function () {
                return Second.query();
            })
            .then(function () {
                return Third.query()
            });
};

$('button').click(function () {
    toRun();
});

Those code are in a testing environment. And by testing environment, I mean a simple HTML page and basic server support for debugging.

When I click the only button on the page, first query returns success and second fails as expected. When I click the button second time, first query skips because it was successful last time and second fails again, also as expected.

The problem here is:

What would be your suggestion to accomplish what I'm trying to do here?

Upvotes: 3

Views: 182

Answers (1)

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276296

Promises represent a single value bound by time. You can't conceptually "reuse" a deferred or reset it - once it transitions it sticks. There are constructs that generalize promises to multiple values (like observables) but those are more complicated in this case - it's probably better to just use one deferred per request.

jQuery's AJAX already provides a promise interface. Your code is mostly redundant - you can and should consider using the existent tooling.

Let's look at $.get:

  • It already returns a promise so you don't need to create your own deferred.
  • It already uses the browser cache, unless your server prohibits HTTP caching or the browser refuses it only one request will be made to the server after a correct response arrived (assuming you did not explicitly pass {cache: false} to its parameters.

If making post requests you can use $.post or more generally $.ajax for arbitrary options.

This is how your code would roughly look like:

$("button").click(function(){
    var first = $.get("/");
    var second = first.then(function(){
       return $.get("/asd");
    });
    var third = second.then(function(){
       return $.get("/qqq");
    });
});

The reason I put them in variables is so that you will be able to unwrap the result yourself later by doing first.then etc. It's quite possible to do this in a single chain too (but you lose access to previous values if you don't explicitly save them.

For the record - it wasn't a stupid question at all :)

Upvotes: 1

Related Questions