An Asynchronous Task List in Apache Cordova

I’ve been spending a bunch of time recently learning Apache Cordova with an eye towards integrating something within Azure Mobile Apps. I want to have an iOS and Android app that works with my TodoItem backend in Node. I also want it to be written in ES2015. So far, I’ve done a bunch of the basic work, but now it’s time to put an app together. You can see most of the work on my GitHub repository.

What I want to consider today is an asynchronous task list store. Each task in the TodoItem has an ID (which is a GUID converted to a string), a text description and a completed flag. I need to be able to read, insert and update records. I’m not going to deal with deletions right now, but that’s coming. To abstract this, I’m going to write an ES2015 class. Let’s start with the basics:

import uuid from 'uuid';

export default class Store {
    constructor() {
        console.info('Initializing Storage Manager');
        
        this._data = [
            { id: uuid.v1(), text: 'Item 1', complete: false },
            { id: uuid.v1(), text: 'Item 2', complete: false }
        ];
    } 
}

I’m creating two example tasks to get me started. I don’t need them, but it helps to show off the HTML coding.

At the center of asynchronous programming in JavaScript is the Promise. Put simply, a promise is a representation of something that doesn’t exist yet. It will be asynchronously resolved and your code can come back to it later. You can use promises relatively simply:

asyncFunction
   .then((result) => {
      /* do something with the result */
   }).catch((error) => {
      /* do something with the error */
   });

You can chain multiple promises and wait for multiple promises to complete. This all results in a rather flexible mechanism to make your code asynchronous. But how do you create a promise? My Store class has an array right now. I want to make it ACT asynchronously so that I can add the network code later on. You need to write a method that either resolves or rejects the promise. Here is the insert() method:

    /**
     * Insert a new object into the database.
     * @method insert
     * @param {object} data the data to insert
     * @return {Promise} - resolve(newitem), reject(error)
     */
    insert(data) {
        data.id = uuid.v1();
        console.log('[storage-manager] insert data=', data);
        var promise = new Promise((resolve, reject) => {
            // This promise always resolves
            this._data.push(data);
            resolve(data);
        });
        
        return promise;
    } 

Creating a promise is a case of creating a new Promise object with a callback function. The callback will be passed “what to call when you are resolving or rejecting the promise”. You then do your processing and call the resolve or reject method to say “I’m done”. Note that I’m using “fat-arrows” to preserve the this variable value. If you don’t use a fat-arrow function then you have to preserve this by other means. All my other functions are similar. For example:

    /**
     * Read some records based on the query.  The elements must match
     * the query
     * @method read
     * @param {object} query the things to match
     * @return {Promise} - resolve(items), reject(error)
     */
    read(query) {
        console.log('[storage-manager] read query=', query);
        var promise = new Promise((resolve, reject) => {
            var filteredData = this._data.filter((element, index, array) => {
                for (let q in query) {
                    if (query[q] !== element[q]) {
                        return false;
                    }
                }
                return true;
            });
            resolve(filteredData);
        });
        
        return promise;
    }  

This will return a list of tasks that match my criteria.

Using this class is encapsulated in my index.js code:

app.on('deviceready', function () {
    var taskStore = new Store();
    
    // Get the various pieces of the UX so they can be referred to later
    var el = {
        todoitems: document.querySelector('#todo-items'),
        summary: document.querySelector('#summary'),
        refreshlist: document.querySelector('#refresh-tasks'),
        addnewtask: document.querySelector('#add-new-task'),
        newitemtextbox: document.querySelector('#new-item-text')
    };
    
    // This is called whenever we want to refresh the contents from the database
    function refreshTaskList() {
      el.summary.innerHTML = 'Loading...';
      console.log('taskStore = ', taskStore);

      taskStore.read({ complete: false }).then((todoItems) => {
          console.log('Read the taskStore: items = ', todoItems);
          let count = 0;
          el.todoitems.innerHTML = ''; 
          todoItems.forEach((entry, index) => {
              let checked = entry.complete ? ' checked="checked"': '';
              let entrytext = entry.text.replace('"', '"');
              let html = `<li data-todoitem-id="${entry.id}"><input type="checkbox" class="item-complete"${checked}><div><input class="item-text" value="${entrytext}"></div></li>`;
              el.todoitems.innerHTML += html;
              count++;
          });  
          el.summary.innerHTML = `<strong>${count}</strong> item(s)`;  
      }).catch((error) => {
          console.error('Error reading task store: ', error);
          el.summary.innerHTML = '<strong>Error reading store.</strong>';
      });
    }

    // Set up the event handler for clicking on Refresh Tasks
    el.refreshlist.addEventListener('click', refreshTaskList);
    refreshTaskList();
    el.newitemtextbox.focus();
});

Note the highlighted line. I call the read() method (above) and wait for it to return the value. This can be asynchronously. Once it completes, the results are passed into the fat-arrow function in the then clause and that renders the list of tasks for me.

The changes to this version are quite extensive, so I encourage you to check out the code at my GitHub Repository. All the ES2015 code is in src/js with the store implementation in src/js/lib/storage-manager.js.

I still have some work to do here. Specifically, I copied (and updated) the Azure Mobile Services Quick Start for Apache Cordova and ES2015. I want to update the CSS3 code to be a little more friendly towards the devices. I also want to implement sorting and filtering. That’s the topic for another blog post.

Promises and AJAX in ECMAScript 6

One of the major things I was using jQuery for was the interactions with my Web API. I am used to called $.getJSON() to get that data and using jQuery Deferred Objects to handle the async nature of the call. Given that with ECMAScript 6, most of the incompatibilities between browsers goes away (and thus a lot of the need for jQuery), what about AJAX?

We still have the XMLHttpRequest() object to do the request. That bit hasn’t changed. A new feature that has changed is Promises. Promises are a basic recipe that can be distilled down to “do something, then do something else, unless you fail, in which case do this other thing”. In Promise land, this turns into:

dosomething()
    .then(function(response) {
        doSomethingElse();
    })
    .catch(function(error) {
        doThisOtherThing();
    });

You can cascade and interleave then/catch to catch things at different positions, but this is the basic format.

Back to my case of collecting data from my Web API. I have to code doSomething() to return a Promise and do it on an event-driven basis. If I do it right, then my code can be this:

$http.ajaxGet(uri)
    .catch(function(error) { throw new AJAXError(error); })
    .then(JSON.parse)
    .catch(function(error) { throw new JSONError(error); })
    .then((r) => this.doSomethingWithJSON(r); })
    .catch(function(error) { throw new ApplicationError(error); });

The basic flow is something like this. Retrieve some data from Web API, then run that data through JSON.parse() to turn it from a string into an object, then go do something with that JSON in my class. In terms of error handling, I’m going to throw a different exception for each step of the way. If I didn’t care about error handling in the individual steps, then I could do the following:

$http.ajaxGet(uri)
    .then(JSON.parse)
    .then((r) => this.doSomethingWithJSON(r); })
    .catch(function(error) { throw new ApplicationError(error); });

This is much simpler to read than the jQuery / ES5 equivalent code. The magic occurs because my ajaxGet method returns a Promise. At the top of my ECMAScript 6 file I’ve got the following:

import $http from "./AJAX";

This brings in my AJAX library. Here is the relevant portion of that library:

function ajaxGet(url) {
    return new Promise(function(resolve, reject) {
        let req = new XMLHttpRequest();
        req.open("GET", url);
        req.onload = function() {
            if (req.status === 200) {
                resolve(req.response);
            } else {
                reject(new Error(req.statusText));
            }
        };

        req.onerror = function() {
            reject(new Error("Network error"));
        };

        req.send();
    });
}

export default { ajaxGet };

Notice how I return a new Promise. When a new Promise is created you pass it a function which has two arguments:

  1. What to call when you have a successful or resolved promise
  2. What to call when you have an unsuccessful or rejected promise

The contents of this function are a pretty standard XMLHttpRequest() logic where I specify two event handlers – one is the onload() which is called when the XMLHttpRequest succeeds. In here I check to ensure I get a 200 back and resolve the Promise if I do (resolving a Promise is a success criteria so it calls the next then… clause). Anything else is an error so I reject the Promise. Once that is all set up, I send the request async.

I have similar functions for ajaxPost, ajaxPut and ajaxDelete – some of them take data and some of them don’t. As long as I’m returning a Promise, I can deal with them using the Promise recipe above.