An example on using promises in a Titanium application.
The master
branch uses Alloy. If you want to see examples based on just the API without Alloy check out the non_alloy
branch.
Table of Contents generated with DocToc
First make sure you have a working titanium environment. NPM will install titanium as a dependency however, it still needs to install the SDK manually. It is recommended you install titanium globally first.
$ npm install -g titanium
$ titanium sdk install 3.1.3.GA
Once ready run the following in the projects working directory.
$ npm install .
$ npm start # start server and compile app OR
$ npm run-script debug # for verbose debug output
This will spawn a small express server for demo uses and then will build the application and spawn it in the iOS simulator.
Promises offer a convenient way to make syntactical and logical sense of the confusing nature of asynchronous code. If your reading this then you most likely know what the patterns are surrounding callbacks and the difficulties they pose in code readability and consistency.
In this project I outlined three use cases where asynchronous code is used and how promises could be implemented to help.
setTimeout
offers a convenient way to delay things. A sample implementation
of such a function could look like:
function delayed(ms) {
var defer = Q.defer();
setTimeout(defer.resolve, ms);
return defer.promise;
}
delayed(1000).then(function () {
console.log("It finished!");
});
// Q does this for you with Q.delay()
A timeout loop is illustrated in app/lib/timeout_promiser.js
. This pattern
can be useful to break up long running processing into small chunks allowing
the current JavaScript context to avoid getting blocked.
Sometimes user interaction is required but your only interested in a one time result. Unlike events which are triggered more than once. Some user interaction is one-off. For example a popup message with choices. Or a modal login screen.
An example of such a component is in app/controllers/modal_popup.js
.
One of the most common use cases is to handle HTTP request. Several
implementation exist which convert to promises. The most basic is illustrated
in app/lib/http_client.js
which uses the Titamium.Network.HTTPClient
module.
For an example on the simplicity of making a promise based getter method that uses Node's HTTP module (this is not Titanium but here for reference and illustration):
http = require('http');
function httpGet(url) {
var data = "", defer = Q.defer();
http.get(url, function (res) {
if (res.statusCode !== 200) {
defer.reject("Bad HTTP: " + res.statusCode);
return;
}
res.on("error", defer.reject);
res.on("data", function(chunk) { data += chunk; });
res.on("end", function() { defer.resolve(data); });
});
return defer.promise;
}
// JSON: {"message":"test"}
httpGet("http://example.com/sample.json")
.then(JSON.parse)
.get("message")
.then(console.log, console.log)
.done();
This project uses jasmine-node to run tests on the application. It is Node based and therefore uses a mocked out version of the Titanium API. Keeping this in mind that tests only test the behaviour of the code written in this application and not the Titanium API. Which means testing that views render correctly is out of scope with this setup.
You run the tests with:
$ npm test
The examples used illustrate how to properly handle the asynchronous nature of promises. Synchronous assumptions in the usual tests will fail or offer inconsistent results when promises are used and require the tests to be written asynchronously.
When testing promise code in Jasmine you have to remember that despite using any mock timer all methods in the Q library implementation are asynchronous.
The following example WILL HAVE PROMBLEMS:
describe "A broken test for promises", ->
it "should call the callback but will fail instead", ->
callback = createSpy "callback"
promise = functionReturnsAResolvedPromise()
promise.then(callback)
expect( callback ).toHaveBeenCalled()
The reason is that the then
method will return immediately but not run the
callback to next tick. So when we attempt to see if the spy was called it
hasn't yet.
To fix this use an asynchronous test pattern:
describe "A working test for promises", ->
beforeEach ->
@promise = functionReturnsAResolvedPromise()
it "should call the callback", ->
ready = false # a flag to poll for when the test can continue
callback = createSpy "callback"
runs ->
# force the flag to finish execution regardles of state of promise.
@promise.fin -> ready = true # turn the flag to true
@promise.then(callback)
waitsFor (-> ready), "promise to be resolved", 10
runs ->
expect( callback ).toHaveBeenCalled()
# Always offer a way for unhandle exceptions to get re-thrown
@promise.done()
A little but more code but now we can guarantee that when we expect the
callback to have been called the then
method will have had a chance to
finish.
Promises Titanium Example by Devin Weaver is dual licensed under a Creative Commons Attribution 3.0 Unported License and a Do What You Want To 3.0 Public License.