Skip to content
This repository has been archived by the owner on Dec 23, 2019. It is now read-only.

Promises

Sean Dunn edited this page Jul 31, 2013 · 8 revisions

Promises can be used to handle async procedures. Typically, jQuery provides them everytime an async operation is called (Think $.ajax()).

in s2_extraction_pipeline

In s2_extraction_pipeline, jQuery promises are used to encapsulate async operations, making them look like atomic.

We are using them to ensure the communication between the model and its controller, the model being in charge of the communications with S2 (which are async by nature).

model.init(...)

The init method of the model returns a promise. This ensures that whatever happens, model methods are always called when the model is ready.

    
    // in the model
    
    init: function () {
        ...        
        return $.Deferred().resolve(this).promise();
    }    
    // in the controller...

    thisController.model = Object.create(Model).init();
    thisController.model.then(function(result){
        result.modelMethod();
    });

model.inputs

Quite often, the model will have to load resources right from the start: the inputs tubes for example. Of course, this is not an immediate action, and should therefore be treated as a promise.

Here is how we solved this problem:

    
        // in the model
    
    setBatch: function (batch) {
        this.batch = batch;
        this.setupInput();
    }

        setupInputs: function () {
            var thisModel = this;
            var inputs = [];
            return that.batch.items
                .then(function (items) {
                    ...                
                })
                .then(function () {
                    return thisModel.inputs = $.Deferred().resolve(inputs).promise();
                });
    }

    ...
    
    doSomething: function () {
        // we need to work with inputs, so...
        this.inputs
            .then(function(inputs){
                console.log(inputs.length);
            });
    }
    
    ...

In this example, one sets the batch first (might be called by the init or the setup method for example). This triggers the call to set the inputs. It is then possible to call doSomething straight away, as it relies on the inputs promise. It will only run when the inputs are ready! We could hence imagine that the user will click on a button calling for the method doSomething(), this will only start to be effective when the inputs (tubes) will be fully loaded.

Error reporting

By nature, it is very hard to report errors properly with promises if not used properly.

Here is a pattern that can be used to provide an efficient (or at least usable) way to do so:

  • return one specific promise per method
myMethod: function() {
        var deferred = $.Deferred();
        ...
        return deferred.promise();
    }

This ensures that operation looks atomic (Please note that philosophically, this is important! A promise acts as a proxy on one value, or at least, on the result of one operation. Wrapping several async operations in one can only be beneficial.)

  • the return promise can only fail or succeed. It sounds obvious, but again, not really. Once a promise has failed, it means that its state has been set, and this cannot be changed anymore.

  • it becomes a bit complicated when jQuery promises are chained... Here is an example:

    bar1()
         .then(foo1)
         .then(foo2)

if bar1() fails, then neither foo1 or foo2 are executed. That's ok.

if foo1() fails, then foo2 is not executed. That's ok.

Let's had some failure statement...

    bar1()
         .then(foo1)
         .fail(err1)
         .then(foo2)
         .fail(err2)

if foo2() fails, then err2 is executed. That's ok.

if foo1() fails, then both err1 and err2 are executed. That's not really what we want!

if bar1() fails, then both err1 and err2 are executed!

To bypass this behaviour, we can make sure that all the failure methods only act on the higher level promise (see first point)

myMethod: function() {
    var deferred = $.Deferred();
        bar1()
            .fail(function(){
                deferred.reject({message:"bar1 failed."});
            })
            .then(foo1)
            .fail(function(){
                deferred.reject({message:"foo1 failed."});
            })
            .then(foo2)
            .fail(function(){
                deferred.reject({message:"foo2 failed."});
            })
            .then(function(){
                deferred.resolve();
            })
        return deferred.promise();
    }

Because the 'deferred' object can only be rejected once, even if all the fail statement are executed, the data returned can only be set on the first failure.

From the outside world, the value of the returned by myMethod() is a promise which can be pending, a success, or a failure BUT, with different values. This means that we can decided what to do according to the type of failure. In practice, this must only be limited error display logging.