Integrating Promises Into Appcelerator Alloy Cloud Services Adapter

Published Apr 14, 2015Last updated Mar 21, 2017
Integrating Promises Into Appcelerator Alloy Cloud Services Adapter

Quick Overview

We all have become accustom to using promises to avoid the callback hell so here we have an example of an ACS adapter that supports promises using the $q javascript library.

First you create your Alloy Appcelerator Cloud Services Models following the steps outlined in the Appcelerator Alloy Model Documentation

When you are done an object looks similar to this; notice how we defined the ACS class and the name of the associated collection to match the class. This is essential because it is how the ACS Sync Adapter can construct the appropriate REST API call to interact with the service

exports.definition = {
    config : {
        "columns" : {},
        "defaults" : {},
        "adapter" : {
            "type" : "acs",
        },
        "settings" : {
            "object_name" : "photos", // <- match the ACS Object
            "object_method" : "Photos"
        }
    },

    extendModel : function(Model) {
        _.extend(Model.prototype, {});
        return Model;
    },

    extendCollection : function(Collection) {
        _.extend(Collection.prototype, {});
        return Collection;
    }
}

For Custom Objects, we can support them by providing the name of the custom object in the configuration setting property and then set the object_method. See example of a custom object called book

exports.definition = {
    config : {
    "columns": {},
    "defaults": {},
    "adapter": {
        "type": "acs",
    },
    "settings": {
        "object_name": "book",
        "object_method": "Objects" //<--indicates a Custom ACS object
       }
    },

    extendModel : function(Model) {
        _.extend(Model.prototype, {});
        return Model;
    },

    extendCollection : function(Collection) {
        _.extend(Collection.prototype, {});
        return Collection;
    }
}

So now you can query your custom object like this

/**
* gets books and returns a promise
*/
function getBooks() {
    var books = Alloy.createCollection('Book');
    return books.fetch();
}

// need a user object to login with
var aUser = Alloy.createModel('User');

// notice the call to the extended function with no success or error
// callbacks, they are handled by the promise structure
aUser.login("testuserone", "password").then(function(_response) {
    // successful login here!!
    Ti.API.info(' Success:Login, with Promise\n ' + JSON.stringify(_response, null, 2));

    // now query for the books and the success will be handled by 
    // the next `then` function below, else it falls thru to the 
    // error 
    return getBooks(); //&lt;-- returns a promise also!
}).then(function(_bookResp) {
    // here we handle the successful book query
    Ti.API.info(' Success:Books, with Promise\n ' + JSON.stringify(_bookResp, null, 2));
}, function(_error) {
    // ANY errors is the promise chain will fall thru to here
    Ti.API.error(' ERROR ' + JSON.stringify(_error));
});

This approach is MUCH cleaner that the old callback approach, give it a try... the adapter still support both approaches.

Additional Changes Required to Get Application to Function Properly

  • You will need to include the $q javascript library in your project. I suggest you create a lib folder in the app directory and add the file there.
  • You add the alloy\sync\acs.js file to the lib folder also, be sure to create the complete folder path.
  • You will need to update your alloy.js file to support the models and collections returning the promise from the sync adapter, see line 10 and line 36 where we return the result from the sync adapter.

The new changes to alloy.js, add the lines below to the file

Alloy.C = function(name, modelDesc, model) {
  var extendObj = {
    model : model
  };
  var config = ( model ? model.prototype.config : {}) || {};
  var mod;
  if (config.adapter && config.adapter.type) {
    mod = require("alloy/sync/" + config.adapter.type);
    extendObj.sync = function(method, model, opts) {
      return mod.sync(method, model, opts);
    };
  } else
    extendObj.sync = function(method, model) {
      Ti.API.warn("Execution of " + method + "#sync() function on a collection that does not support persistence");
      Ti.API.warn("model: " + JSON.stringify(model.toJSON()));
    };
  var Collection = Backbone.Collection.extend(extendObj);
  Collection.prototype.config = config;
  _.isFunction(modelDesc.extendCollection) && ( Collection = modelDesc.extendCollection(Collection) || Collection);
  mod && _.isFunction(mod.afterCollectionCreate) && mod.afterCollectionCreate(Collection);
  return Collection;
};

Alloy.M = function(name, modelDesc, migrations) {
  var config = (modelDesc || {}).config || {};
  var adapter = config.adapter || {};
  var extendObj = {};
  var extendClass = {};
  var mod;
  if (adapter.type) {
    mod = require("alloy/sync/" + adapter.type);
    extendObj.sync = function(method, model, opts) {
      return mod.sync(method, model, opts);
    };
  } else
    extendObj.sync = function(method, model) {
      Ti.API.warn("Execution of " + method + "#sync() function on a model that does not support persistence");
      Ti.API.warn("model: " + JSON.stringify(model.toJSON()));
    };
  extendObj.defaults = config.defaults;
  migrations && (extendClass.migrations = migrations);
  mod && _.isFunction(mod.beforeModelCreate) && ( config = mod.beforeModelCreate(config, name) || config);
  var Model = Backbone.Model.extend(extendObj, extendClass);
  Model.prototype.config = config;
  _.isFunction(modelDesc.extendModel) && ( Model = modelDesc.extendModel(Model) || Model);
  mod && _.isFunction(mod.afterModelCreate) &amp;&amp; mod.afterModelCreate(Model, name);
  return Model;
};

Latest Video From Series

Discover and read more posts from Aaron Saunders
get started