1

I'm trying to use the best practics with Javascript but it's so hard for me.In this case I wanna use some function "avoid" the callbacks. I'm trying to use the Deferred object of jquery to do it. So I believe that I don't how to works fine.

I'm working with phonegap + emberjs + cordova-sqlite-plugin.

I've the follwing function that implements callbacks.

getPuntos: function(callback)
{
    var db = window.sqlitePlugin.openDatabase("Rondas", "1.0", "Dev", -1);

    var ret ={data:false};
    db.transaction(function(tx)
    {
        tx.executeSql("SELECT * from Punto;",[], function(tx, res)
        {
            if( res.rows.length !== 0)
            {
                var puntos =  [];
                for( var i=0; i<res.rows.length; i++)
                {
                    var descripcion = res.rows.item(i).descripcion;
                    var id = res.rows.item(i).id;
                    //console.log( descripcion );
                    var punto = App.Punto.create( { index: i, id:id, descripcion: descripcion});
                    puntos.push( punto );
                }
                ret.data = puntos;
                callback(ret.data);
            }

        });
    },function(tx,err){
        console.log('Error SQL'+err);
    },function(){
        console.log( 'Base de datos abierta' );
    });     
}

and I use this as well:

this.getPuntos(function(puntos){
   for( var i=0; i<puntos.length; i++){
     console.log( puntos[i] );
   }
});

Well, but I try to implements something very clear and easy like:

//whitout use a callback.
var puntos = this.getPuntos();
for( var i=0; i<puntos.length; i++){
     console.log( puntos[i] );
}

Then I try to do it:

  1. Change the function getPuntos to _getPuntos, this function has a deferred obeject.
  2. the function getPuntos is wrapper to handle the result of promise getting in the method _getPuntos and try to return.(but not works)
_getPuntos: function()
{
    var db = window.sqlitePlugin.openDatabase("Rondas", "1.0", "Dev", -1);
    var deferred = jQuery.Deferred();   
    var ret ={data:false};
    db.transaction(function(tx)
    {
        tx.executeSql("SELECT * from Punto;",[], function(tx, res)
        {
            if( res.rows.length !== 0)
            {
                var puntos =  [];
                for( var i=0; i<res.rows.length; i++)
                {
                    var descripcion = res.rows.item(i).descripcion;
                    var id = res.rows.item(i).id;
                    //console.log( descripcion );
                    var punto = App.Punto.create( { index: i, id:id, descripcion: descripcion});
                    puntos.push( punto );
                }
                //ret.data = puntos;
                //callback(ret.data);
                deferred.resolve(puntos);
            }

        });
    },function(tx,err){
        console.log('Error SQL'+err);
    },function(){
        console.log( 'Base de datos abierta' );
    });
    return deferred.promise(); 
},

The wrapper:

getPuntos: function()
{
    var promise  = this._getPuntos();
    var ret = {data:false};
    promise.done(function(result)
    {
        ret.data = result;
        return ret.data;
    }); 
    while(ret.data ===false ){} //wait for the result until it's available
    return ret.data;        
},

In the main function I call it :

var puntos = this.getPuntos();
console.log( puntos+"--shoulbe [object]"); //I get Undefined or false, but no my array.

So, is there some way to do that I wanna, convert a asynchronous function in synchronous function?.

Thanks for all your answers.

Cristian
  • 1,480
  • 5
  • 32
  • 65
  • 1
    No. Promises don't magically make a function synchronous (which would defeat the point of concurrency). They're just a good [abstraction over callbacks](http://stackoverflow.com/a/22562045/1048572). – Bergi Jul 27 '14 at 22:01

2 Answers2

2

The main problem here is that your getPuntos() function in the second example does not return anything hence the null/false value.

getPuntos: function() 
{
    var promise  = this._getPuntos();
    var ret = {data:false};
    promise.done(function(result)
    {
         // This happens in the future
         ret.data = result;
         return ret.data;
    });    
// This happens now and there is nothing returned 
},

When your promise is completed (promise.done) it returns ret.data but this is returned to the promise and is ignored. It is in the wrong scope. You thinking in a traditional 'pull' model where data is pulled towards you and the program blocks until data is available. Javascript uses callbacks and is more of a 'push' model where data is pushed onto callbacks when it is available and there is no blocking.

You need to rearrange things. For example the getPuntos function should return the promise.

getPuntos: function() 
{
    var promise  = this._getPuntos();
    return promise;
},

And the main function should add a callback for when the promise is fulfilled.

var puntos = this.getPuntos();
puntos.done(function(result) {
    console.log( result+"--shoulbe [object]");
});

Although the getPuntos() function is a bit redundant here as it is just returning the value of _getPuntos().

Sean Dawson
  • 5,587
  • 2
  • 27
  • 34
1

The problem is that your getPuntos() function sets up the promise.done action, but the returned value never gets passed to the rest of your program. As NoxHarmonium said, your getPuntos() method is superfluous and should be combined with _getPuntos(), but you probably want something like the following (as you can see in this jsFiddle):

PuntosPromise = function() {
    this.result = null;
    this._getPuntos = function() {
        var deferred = $.Deferred();
        setTimeout(function(scope) {
            scope.result = "It works."
            deferred.resolve();
        }, 500, this);
        return deferred.promise();
    }
    this.getPuntos = function()
    {
        var promise  = this._getPuntos();
        return promise;        
    }
    this.getResults = function() {
        return this.result;
    };
}
$("#tester").click(function() {
    var test = new PuntosPromise();
    $.when(test.getPuntos()).then(function() {
        alert(test.getResults());
    });
});

EDIT:

See @Bergi's modifications given below. While this code works, his solution is cleaner.

Ryan Mitchell
  • 754
  • 5
  • 8
  • a) Don't use the name `Promise` for anything that is not a promise constructor (yours is a custom puntos-getter-whatever) b) don't use a `result` variable - just resolve the deferred! – Bergi Jul 27 '14 at 22:12
  • @Bergi Thanks for the advice. As to (a), I would never do that in actual code; I was just writing quickly and sloppily. As to (b), could you explain why what I wrote is bad practice? I personally like the additional abstraction it gives, but that's mostly just aesthetic: I find this more readable. – Ryan Mitchell Jul 27 '14 at 22:16
  • 1
    It's bad practise because the deferred itself is capable of storing the result. A promise does represent a future (result) value, not the completion of a task. Please don't tell me that [this (half loc)](http://jsfiddle.net/xUDX3/1/) is less readable. – Bergi Jul 27 '14 at 22:19
  • @Bergi Thank you for succinctly and clearly answering my question. – Ryan Mitchell Jul 27 '14 at 22:25
  • Thanks for your answers, but the aim of the function `getPuntos` is return a single result ,no a callback, if I can do `var test= new PuntosPromise()` and then whitout nothing `var r = test.getResults()` awesome. – Cristian Jul 27 '14 at 22:41
  • @CristianChaparroA. As I understand it, promises just don't work that way. Maybe something [like this](http://jsfiddle.net/posgarou/xUDX3/2/) would work better for you? `function getPuntos(puntosHandler) { var deferred = $.Deferred(); setTimeout(function() { deferred.resolve("It works.") }, 500); deferred.promise().done(puntosHandler); return deferred.promise(); } function resultHandler(resultFromPuntos) { alert(resultFromPuntos); } $("#tester").click(function() { getPuntos(resultHandler); });` – Ryan Mitchell Jul 27 '14 at 22:50
  • I'm so wrong, it's mean that there aren't way to convert an asynchronous function to synchronous function, thus eliminating the callback.In the better case I need to use a nested `then` from promise, like the @Bergi suggestion – Cristian Jul 27 '14 at 23:05
  • @CristianChaparroA.: Yes, exactly. – Bergi Jul 27 '14 at 23:19