Using Promises with Appcelerator Titanium

by | Aug 3, 2015 | 4 comments

One of my favorite parts of the JavaScript language is its asynchronous nature. You can easily pass functions as parameters to other functions and execute them as callbacks. This is one of the first things any JavaScript developer has to learn when dealing with asynchronous tasks such as net requests or animations. The code you write might not be executed in the order you expect when dealing with asynchronous tasks.

So how can we keep everything straight? It can be difficult if only using callbacks. Chaining anonymous callback functions together is a good way to get confused and write code that is hard to maintain.

There has to be a better way!

And there is. Using Promises, you can encapsulate your asynchronous code into easy to digest snippets that can be chained together to make your life easier. Here at Shockoe, we mostly use RSVP.js (you can download version 3.0.16 which is compatible with Titanium on their github page) because it is es6 compliant and has a few other cool features.

To get started using RSVP.js in your Appcelerator Titanium project, download the source code from their github page, and copy that file to the lib folder in your project with the name RSVP.js. Now, all you need to do in your controller file is save it to a var with the require function.

Below is a simple controller file that will animate three views on screen after pressing a button. After the last animation has completed, an alert will be shown.

var RSVP = require('RSVP');

function start(e) {
	animateBounce($.topView)
	.then(animateBounce($.middleView), promiseRejected)
	.then(animateBounce($.bottomView), promiseRejected)
	.then(afterAllAnimations, promiseRejected);
}

var animateBounce = function(view) {
	return new RSVP.Promise(function(resolve, reject) {
		view.animate({
			left : 200,
			autoreverse : true,
			duration : 600
		}, resolve);
	});
}

var promiseRejected = function(data) {
	//Print rejected data
	Ti.API.info(data);
}

var afterAllAnimations = function(data) {
	alert('all animations complete');
}

$.index.open();

The start function is a good example of what is called “promise chaining”. Promises can easily be linked together. You just have to make the function return another promise.

Because we are waiting for all of the promises to complete before moving on, we can use the all() method to make things even easier. This takes an array of promises as a parameter and returns another promise that will be fulfilled after all the promises have been fulfilled or rejected after a promise has been rejected.

Here is the start() function using an array of promises and all()

function start(e) {
	RSVP.all([animateBounce($.topView), animateBounce($.middleView), animateBounce($.bottomView)])
	.then(afterAllAnimations, promiseRejected);
}

Using the all() function can make long chains of promises easier to read and work with.

Another great use for promises is net requests using the Titanium Network.httpClient. It may seem very simple, but using promises with net requests can save time, and make your code more reliable. Below is an example of a simple net request using promises.

function makeNetRequest(url) {
	return new RSVP.Promise(function(resolve, reject) {
		var xhr = Ti.Network.createHTTPClient({
			onload : function() {
				resolve(this.responseText);
			},
			onerror : function() {
				reject(this.status);
			}
		});

		xhr.open('GET', url);

		xhr.send();
	});
}

makeNetRequest('http://www.google.com/')
.then(function(data) {
	//Successful response from our net request
	foo(data);
}, function(status) {
	//Error in the request, check status code
	bar(status);
});

In our HTTPClient, the resolve and reject function of the promise are used in the HTTPClient’s onload and onerror functions respectively. This makes it extremely easy to do specific handling of the data that comes back from a successful response, and it allows us to do specific error handling.

Last, but not least, is the finally() function which will be invoked regardless of whether or not the promise was resolved successfully or not. This is useful for when there is some task that needs to wait until after the net request is finished, but must be run regardless of the outcome. I have modified the last example to use finally()

makeNetRequest('http://www.google.com/')
.then(function(data) {
	//Successful response from our net request
	foo(data);
}, function(status) {
	//Error in the request, check status code
	bar(status);
})
.finally(finishNetRequest);

The function finishNetRequest() will always be executed and is a good place to put any code that needs to be run after the net request finishes.

Almost every project we work on here at Shockoe uses some kind of asynchronous task. Promises give us an easy way to encapsulate, chain, and resolve all these tasks in an easy to read and easy to maintain fashion. Moving forward, Promises will be at the heart of all of our projects.