Blog

31
January 2015

Gavin Pickin

Using WebSQL - Pulling Data - Start Thinking Async vs Sync

Javascript, WebSQL

In today's post on WebSQL, I’m going to show you how I had to refactor my application, to move from sync LocalStorage functions and storage, over to using WebSQL, which has an ASYNC API for transactions. In the last few posts, I have gone over why I chose WebSQL for this Application, and how to get Started with WebSQL etc, and had to post a correction to some assumptions I had made, so now we’re ready to move on and get some data out of the db, and thrown into our application.

So first we’ll start with my localStorage version of the code. I admit, I’m not a veteran Javascript programmer, so I am not up to speed on all the best practices etc, although, I’m learning all the time, so hopefully its not the worse code you’ve seen. If you think I should do things differently, let me know, this is part of the reason I blog, to get code out there, and get feedback, or alternative solutions etc.

This method is my renderTrainingList, its basically in a View/View Model Layer… and it calls the trainingService to get a list of trainings, and outputs them using a Mustache Template, and throws it into the dom. I am not 100% happy with the mix of duties here, I will probably move these into a controller now that I think of it. Save that for another post now though.

//trainingService.js
renderTrainingList: function( ){
               var template = $('#trainingListRowTemplate').html();
               console.log('start render function');
               trainingListData = app.trainingService.getTrainings();
               console.log('got the training data');
               Mustache.parse(template);   // optional, speeds up future uses
               $('#training-list tbody').html('');
               for (var i = 0; i < trainingListData.length; i++){
                    var rendered = Mustache.render(template, trainingListData[i] );
                    $('#training-list tbody:last').append( rendered ).trigger('create');                                                    
               }
}

Trying to employ equivalent separation of concerns, my service (behaving like a DAO almost) looked like this

//trainingService.js
getTrainings: function() {
               try {
                    return JSON.parse(localStorage['trainings']);    
               }
               catch (e){
                    return [];
               }   
},

So the first thing I have to do it change the function that actually gets the data… I decide to split the service, and the DAO out a little more, incase I want to do more Service like processing, but I want the DAO just to be simple data in or out. So updating my service, looks more like this.

//trainingService.js
getTrainings: function() {
     return app.trainingDAO.getTrainings();
}

I can add extra processing here if needed, but the guts of the sql, and the implementation details will be black boxed in the DAO, how they should have been.

getTrainings: function() {
     app.db.readTransaction(function (tx) {
          tx.executeSql('SELECT * FROM training',
               [],
               successCall,
                              errorCall
                 );
          });
     });
}

Now, we have a problem… I cannot return the list of trainings, because its an async call. I do not have anything to return, but I can call something else. Maybe I should create another method in the service, and then call that.

This smells funky to me… and for good reason. I would be calling from the DAO back to the Service or ViewModule with the data, to do something with. I want my DAO to be independent of whats calling it, right? So, how do we do this?

Javascript has something called Promises, which will become Native in ES6, but for now, are usable within libraries like Q or jQuery etc. How they work is essentially, you create an object, called a deferred, which essentially has has a promise - a piece of work to be done, and then code to be run when it is complete, or when it fails. Of course, that is a simple explanation, and you should definitely read more about Promises and Deferred, but I’m going to show you how I use them.

So, lets start at the ViewModule. Instead of the following line of code, which asks for trainings, and stores them in a variable called trainingListData, we want to use a WHEN construct.

//old
trainingListData = app.trainingService.getTrainings();
//new
$.when( app.trainingService.getTrainings() ).done( function( trainingListData ){
     //DO SOMETHING
});

Essentially, we’re saying, When the request for app.trainingService.getTrainings() is completed or RESOLVED in this context, if it is DONE, then do something. Done takes a function, and in this case, we want to use the trainingListData and then do something with it. We put all of our previous sync code into this when block, and instead of it running right away, before the getTrainings() is complete, it waits, and the deferred processes it after we have the data back.

To use a deferred object, in the DAO we need to make one, and we return the deferred object to the WHEN, so it can track when its done. The When is subscribing for a RESOLVED event, essentially like PUB SUB, but Promises can only be resolved 1 time, but read the docs for more information. Here is the DAO using Promises.

// trainingDAO.js
getTrainings: function() {
     return $.Deferred(function (d) {
          app.db.readTransaction(function (tx) {
               tx.executeSql('SELECT * FROM training',
                    [],
                    function(tx, data){ d.resolve(data); },
                    function(tx, error){ d.reject(error);}
               );
          });
     });
}

Basically we are returning a $.Deferred jQuery object. We wrap the WebSQL transaction inside a function, and the successCallback and errorCallbacks using the d variable (the deferred), and either resolve it, passing the data, or reject it, passing the error.

So now, our WHEN code waits patiently async, and if the query is successful, the deferred get resolved, and the When’s .DONE() is run. If it errors, if we have a .fail() on the When it would run. For the when to work, the call must get a Deferred object… but since our ViewModule calls our service which calls our DAO, so how do we pass the deferred? Easy, just return what the DAO returns. If we were wanting to do more processing, we’d have to wrap the processing, including the DAOs deferred in another deferred, but this works, for now :)

//trainingService.js
getTrainings: function() {
     console.log('getTrainings - returning the deferred');
     return app.trainingDAO.getTrainings();
}

So lets look at our render function again, using the WHEN

//trainingService.js
renderTrainingList: function( ){
     var template = $('#trainingListRowTemplate').html();
     console.log('start render function');
      $.when( app.trainingService.getTrainings() ).done( function( trainingListData ){
               console.log('got the training data');
               Mustache.parse(template);   // optional, speeds up future uses
               $('#training-list tbody').html('');
               for (var i = 0; i < trainingListData.length; i++){
                    var rendered = Mustache.render(template, trainingListData[i] );
                    $('#training-list tbody:last').append( rendered ).trigger('create');                                                    
               }
      }); //END OF THE WHEN
}

We replaced the trainingListData statement with the WHEN line, and then app the rest of the code in the DONE block.
There is one more adjustment we have to make. Previously, the localStorage was passing back an array of objects, but WebSQL by default gives you back a WebSQLResult set, so you have to deal with the data differently. If I was trying to be really DAO independent, I would use some conversion service to return an array of objects too, maybe with some metedata from the sql, but I want to use the WebSQL format, especially when I am learning more about it… so lets convert our View Module code, so it knows how to iterate over it.

For length… instead of trainingListData.length we need to reference the rows object, so we use trainingListData.rows.length.

For the actual item, we need to reference trainingListData.rows.item(i) instead of trainingListData[i]. The beauty of WebSQL is, that each item is stored as a javascript object, so at that point, they are the same.

// trainingService.js
renderTrainingList: function( ){
     var template = $('#trainingListRowTemplate').html();
     console.log('start render function');
     $.when( app.trainingService.getTrainings() ).done( function( trainingListData ){
               console.log('got the training data');
               Mustache.parse(template);   // optional, speeds up future uses
               $('#training-list tbody').html('');
               for (var i = 0; i < trainingListData.rows.length; i++){
                    var rendered = Mustache.render(template, trainingListData.rows.item(i) );
                    $('#training-list tbody:last').append( rendered ).trigger('create');                     
               }
     });
}

There isn’t a lot of code to change for the switch, but its a different way of thinking, especially coming from a ColdFusion background, when you call a DAO it waits for the data to proceed, Deferreds and Promises are very cool and elegant way to solve the problem, and keep you out of CALLBACK HELL as most people call it.

Hope it helps.
Thanks for reading.
 

Blog Search