in Appcelerator Titanium, Mobile

Using JavaScript Promises in Appcelerator Titanium

Update: Beginning with SDK 7.1.0, Titanium supports ES6 features, including Promises, by using the transpile flag in tiapp.xml. This means you no longer have to import the Bluebird library in order to use Promises in Titanium.

Intro

In this tutorial, we are going to take the xhr.js library we built in Creating an Alloy Library for Appcelerator Titanium and adapt it to use JavaScript Promises.

Why Promises?

JavaScript Promises have been around a few years, and are a handy way to make asynchronous callbacks easier to work with in your JS code. Since the rise of node.js and its non-blocking i/o, asynchronous callbacks are everywhere. Promises, once understood, can go a long way to helping you avoid getting stuck with a bunch of callbacks.

For more info on JavaScript Promises, see my notes.

And if you want some background on the xhr.js alloy that we will be working with, go here.

Promises in Appcelerator

Promises are an official part of JavaScript as of of ES6, however, Appcelerator Titanium doesn’t yet support ES6. Not to fear, we can still use Promises in Titanium with one of many popular libraries.

Since we need to use a Promise library for now, let’s get one installed and set up. For our demo, we will use bluebird, a well-supported library that is compliant with the ES6 version.

Step 1. Install the library

We will be using the minified version Bluebird’s core library (Get it here).  You can go with the ‘full’ version if you want, just keep in mind that (in both flavors) not all of the library features are available in the ES 6 version of Promises.  This would be an issue for you if later on ES6 (and Promises) are supported in Titanium, and you want to switch to those instead of bluebird.  Otherwise, you could just stick w/ bluebird for your app.

Download the promises.core.min.js file and place it in the app/lib folder next to your xhr.js library from the previous tutorial.

All we need to do now is require the bluebird library just as we would  regular Alloy library:

var Promise = require('promises.core.min');

Ok, enough with the setup. Provided you followed all the steps, (and I explained them correctly), then when you build your Titanium project, you should see the bluebird library being added at compile time, and then the app should launch ok.

Step 2. Using Promises in Our Titanium App

Now that we have Promise support in our Titanium app, let’s do a simple test.

In alloy.js, try this:

//alloy.js or some controller

var Promise = require('promises.core.min');

var myPromise = new Promise(function(resolve, reject){ 
  //we're faking out a slow async request with setTimeout
  setTimeout(resolve, 1000, 'done!'); 
}); 

//myPromise is now a 'thenable', which means we can 
myPromise.then(function(res){
  console.log(res); //done!
});

 

If all goes to plan, you will see ‘done!’ message in the console.

Step 3. Modifying xhr.js to use Promises

Now that we have a promise library working in Titanium let’s use it with the xhr.js lib we created previously.

Issues with the Traditional Callback model

Sooner or later, your app is going to make a request to a remote endpoint, fetch some data, and then do something with that data when it returns to the app. Doing these 3 steps on their own is fairly simple, but what happens when we have to do multiple fetches, or we need to do several things in the app after the data returns, such as update the UI, or notify the user? Things start to get complicated.

This example is pretty straightforward: we’re doing one request and are handling the return data in a single callback function. The issue arrises is what happens if we have multiple requests to make, such as if we have to do an initial GET request to the api, then do a follow up request to a different endpoint based on the data returned from the first one. We’ll have to nest the followup xhr.send() inside the onSuccess() callback of the initial request.

//our first request
xhr.send({
  url: 'http://example.com/api/firstendpoint?val='res.value,
  method: 'GET',
  success: doNextRequest,
  error: function(e){
    //handle error
  }
});

//doing a second request from the initial callback
function doNextRequest(res){

  //pull some data out of the response object
  //and send to another endpoint
  xhr.send({
    url: 'http://example.com/api/asecondendpoint?val='res.value,
    method: 'GET',
    success: function(res){
      //do something with result data from both first and second requests?
    },
    error: function(e){
      //another error handler
    }
  });
}

Ok, you’re starting to see the problem. We’d need a way to chain these together. And, what do we do about error handling?

Switching to Promises

Moving our xhr.js library to use Promises is pretty easy. Since the Promises will be created by xhr.js lib, we will require the promise library there. Note that you can still require the Promise lib elsewhere (or make it a global) if you choose to use promises in other parts of your app.

//xhr.js

//Note: this is a simplified example of our xhr lib with Promises. See the link for the full version!

//add our promise library
var Promise = require('promises.core.min');

function send(args) {
  return new Promise(function (resolve, reject) {
    var request = Titanium.Network.createHTTPClient();

    request.onload = function() {
      resolve(this.responseText);
    };

    request.onerror = function(e) {
      reject(e);
    };

    request.open(args.method, args.url);
  });
};

exports.send = send;

Using the Promisified xhr.js lib

Now when we go to run our example, we can do something like this:

//index.js or elsewhere
var xhr = require('xhr');

//here is where we make the request
xhr.send({
    url: 'http://example.com/api/ourendpoint',
    method: 'GET'
})
.then(function(res){
  //where are callback data would go
})
.catch(function(e){
 //optional error handler
});

Our code looks pretty similar to how it did when we were using the old version of xhr.js with traditional callbacks. The power of promises comes in to play when we start chaining promises.

//snippet...

xhr.send({
    url: 'http://example.com/api/ourendpoint',
    method: 'GET'
})
.then(function(res){
  //make a 2nd request with data from the first response
  return xhr.send({
    url: 'http://example.com/api/endpoint_number_two?someParam=' + res.dataFromFirstRequest,
    method: 'GET'
  });
})
.then(function(res){
  //data from the 2nd callback
})
.catch(function(e){
  //this now catches errors from both requests
});

Now we are getting somewhere! Trying to do this with callbacks would get a little complicated, plus we’d have to do more work to catch all errors.

A More Complex Example

What if we have several requests to make at once, and we don’t necessarily need to chain them together, but we need to know when all the requests have completed? For this, we can use Promise.all.

Promise.all() simply takes an array of promises, and it will then produce a promise that will resolve once all the other promises have resolved. Let’s do a quick example:

//snippet...

var endpoints = [
  'http://example.com/api/one',
  'http://example.com/api/two',
  'http://example.com/api/three',
  'http://example.com/api/four'
];

var promises = [];
endpoints.forEach(function(a){
  //add all the promises from our xhr.send() to an array
  promises.push(xhr.send({url: a, method:'GET' });
});

Promise.all(promises);
.then(function(vals){
 //vals will be an array of responses from each of our xhr.send() requests
});

It would have been a challenge to handle all of those asynchronous requests, responses and potential errors using traditional callbacks. Using our promisified xhr library and Promise.all, we are making it a lot simpler.

Live Demo Time: TiStarWars

TiStarWars-characterDetail-Android

We can only go so far with our demo code here, so let’s try out our xhr library with promises with a real live Appcelerator app. For this, I have created TiStarWars. This app uses the excellent and free [Star Wars API](sw
api.co) to fetch live data via our xhr library with promises. No API authentication required, just launch the app and go.

The TiStarWars app is currently laid our in 3 parts:

  • index.js, which is the main or root window where I fetch all the films via https://swapi.co/api/films/ and display it using a promise.

  • filmDetail.js, where we display the movie details, as well as do another fetch for all the characters in that movie. In this case, are fetching all the characters and adding them to Promise.all() so we know when all the request are complete and can display the list.

  • And characterDetail.js, where we are doing a couple of individual xhr requests to get additional details on the character.

In TiStarWars, I am using Backbone Collections to track data and bind to tableviews, though you can use whatever you like to handle this, including Alloy Collections.

Check it out here: https://github.com/adampax/TiStarWars

Conclusion

Hope this was useful for you. Feel free to share or post any questions or issues.