Making Some Promises (in Parse, but also relevant to jQuery and Backbone)

What a confusing topic. Unfortunately, if you start wanting to add “library” features to your code, like I did, you have to study Promises.

These are notes about Parse.Promises, and the end product is a small object that caches models.

What is a Promise?

It’s a chain of functions. This is the most common form.

var result = promise.then( callback (x) ).then( callback (y) ).then( callback (z) );

What makes it unusual is that this chain of functions will resolve in the future. By “resolve”, I mean return a value. With Promises, the return value of each then() is passed to the next then() and used as input into the callback. The entire chain resolves to the value returned by the lass then(). It’s a little bit like a pipeline, except it’s sequential. It’s also asynchronous. Each call to then() returns a Promise immediately, so the chain is constructed before all of the functions complete execution.

Inside each callback, you might have an asynchronous function, like a call to a REST API over the network. The Promise chain can sequence them.

There’s more to it, but that’s partly what’s relevant to this note.

There’s another kind of Promise created with Parse.Promise.as(value). This is a promise that resolves to the value.

Why not just return the value like “return value;”? Because when you’re programming with promises, you are managing the chain of promises, not passing values around. It’s like two different tasks related to plumbing: building pipelines, and passing water. You use promises to make the pipes. You’ll pass the water later – but don’t mix up the two things.

.as() constructs a promise that contains the value. It injects the value into the promises.

What follows is some demo code.

This is a pretend-cache. There’s no slow service that’s backing it, but I wrote it to understand Promises. There are a lot of good tutorials out there to read.


        function NetCache() {
            this.data = {'x':100, 'y':200};
        }
        NetCache.prototype.lookup = function(id) {
            var res;
            if (this.data[id]) {
                console.log("in lookup, then clause");
                var promise = Parse.Promise.as(this.data[id]);
                return promise;
            } else {
                this.data[id] = "set value for id to "+id;
                var promise = Parse.Promise.as(this.data[id]);
                return promise.then(function(user) {
                    console.log("in lookup, else clause 1, value of " + user);
                    return "from first promise";
                }).then(function(user) {
                    console.log("in lookup, else clause 2, value of " + user);
                    return "value from second promise";
                });
            }
        };
        var x = new NetCache;
        x.lookup('x').then(function(z) { console.log(z); });
        x.lookup('y').then(function(z) { console.log(z); });
        x.lookup('z').then(function(z) { console.log(z); });
        x.lookup('foofoo').then(function(z) { console.log(z); });
        x.lookup('z').then(function(z) { console.log(z); });
        console.log(x);

The function name is a misnomer, because we don’t use the net, but it does behave like a warmed up cache.

The else clause demonstrates how we might construct a chain of promises, and then return that. When you chain with .then() the value returned by the callback function is passed as the value into the next callback function.

Just paste it into a page and run it. The results in Firebug are:

in lookup, then clause
100
in lookup, then clause
200
in lookup, else clause 1, value of set value for id to z
in lookup, else clause 2, value of from first promise
value from second promise
in lookup, else clause 1, value of set value for id to foofoo
in lookup, else clause 2, value of from first promise
value from second promise
in lookup, then clause
set value for id to z
NetCache { data={...},  lookup=function()}

Notice how both branches of code return a Promise object.

If there’s a cache hit, we use .as() to create a promise that will quickly resolve to that value.

If there’s a cache miss, we construct a more complex chain of promises that contains a sequence of functions.

Here’s the code for a Parse.User caching object.


        function UserCache() {
            this.user_cache = {};
        }
        UserCache.prototype.lookup = function(id) {
            var self=this;
            if (typeof this.user_cache === 'undefined') {
                this.user_cache = {};
            }
            if (typeof this.user_cache[id] === 'undefined') {
                var u = new Parse.User({objectId: id});
                return u.fetch().then(function(user) {
                    self.user_cache[id] = u;
                    return u;
                });
            } else {
                var promise = Parse.Promise.as(this.user_cache[id]);
                return promise;
            }
        };

The main difference here is that there’s only one use of .then() rather than the two in the demo. Also, the slow network request to Parse.User’s fetch() is present.

This object is used like this:

var userCache = new UserCache;
userCache.lookup(userid).then(function (user) { ... });

The call to lookup() returns a promise, and that promise is then called with then(), which again returns a promise.

Another way to look at this is to consider that userCache.lookup() replaces itself with a promise chain. If it’s a cache hit, it’s as(value).then(…). If it’s a cache miss, it’s fetch().then(….).then(….).

In any event, this is a so-so solution, but works for me now. It’s main deficiencies are that it’s not general, and that if you request ten different instances of a User, it won’t block any of them. Instead, it will stupidly make all those calls.

Here’s a more general version of a Parse model cache:


        function ModelCache(model, key) {
            this.cache = {};
            this.key = key;
            this.model = model;
        }
        ModelCache.prototype.lookup = function(id) {
            var self=this;
            if (typeof this.cache === 'undefined') {
                this.cache = {};
            }
            if (typeof this.cache[id] === 'undefined') {
                var query = new Parse.Query(this.model);
                if (this.key == 'objectId') {
                    return query
                        .get(id)
                        .find()
                        .then(function(val) {
                            self.cache[id] = val;
                            return val;
                        });
                } else {
                    return query
                        .equalTo(this.key, id)
                        .find()
                        .then(function(val) {
                            self.cache[id] = val;
                            return val;
                        });
                }
            } else {
                var promise = Parse.Promise.as(self.cache[id]);
                return promise;
            }
        };

        var boardCache = new ModelCache(Boards, 'BoardNum');
        var gameCache = new ModelCache(Games, 'objectId');

I haven’t tested this yet, but the second situation, where the key is not ‘objectId’ works fine.

Again, notice that we don’t return values from the cache. Instead, we return a promise that will eventually return the value. That value is passed to the next step in the promise chain.

Warming up the cache!

Now, rationally, we don’t want to allow a dozen or more identical requests to the REST API, but it will happen with this cache. Since I’m a newbie to this I’m not going to alter the cache, but here’s a technique to preload a small set of objects before moving on to the next step. It uses the Parse.Promise.when() function, which resolves all the promises passed to it before continuing to resolve the chain.

Results contains a list of game objects, and each has a Player property. We warm up the cache by extracting all the player IDs, removing duplicates, and then doing a lookup on that list.

We use when() to sequence it.


                        var pids = _.map(results, function(elm) {
                            var player = elm.get('Player');
                            return player.id;
                        });
                        pids = _.uniq(pids);
                        var promises = _.map(pids, function(pid) {
                            return userCache.lookup(pid);
                        });
                        Parse.Promise.when(promises).then(function() {
                            _.map(results, function(elm) {
                                var player = elm.get('Player');
                                userCache.lookup(player.id)
                                .then(function(user) {...});
                            });
                        });
 

when(promises) resolves complete, the then() is called. The code inside then() will hit the cache for each userCache.lookup().