Jun 3
Chaining is a common technique that has been widely adopted by modern JavaScript libraries to chain method calls together.
The goal of chaining is to produce elegant and concise code that is easy to understand or maintain. For example, if you are a jQuery developer, you may produce similar code like this on daily basis.
$('#mydiv').empty().html('Hello Word!').css('font-size','10px');
However, most popular JavaScript libraries only support “static” chaining, e.g. DOM object manipulation. If the method to be chained makes Ajax calls, you will have to resort to callback which requires very strict and verbose syntax.
Again, let us take a look at a jQuery example that chains two serial Ajax calls.
$.ajax({
url: 'service/endpoint1',
success: function(data1) {
// we have successfully made our first ajax call
// and we are ready for our second ajax call
$.ajax({
url: 'service/endpoint2',
success: function(data2) {
// we have successfully chained two ajax calls
},
error:function(error2) {
// handle error from the second ajax call
}
});
},
error:function(error1) {
// handle error from the first ajax call
}
});
The callback approach used by the above example looks clean and will do exactly what we expect. However the challenge is when we need to chain very large number of ajax calls the above code will grow significantly. With many levels of nesting, it will become very unpleasant to read or maintain and you will really miss the simplicity that chaining brings.
Before we introduce Cloud CMS chaining, let us look at another common use case of making parallel Ajax calls. It is easy to make concurrent Ajax calls but the challenge is to find out when all parallel calls finish and then execute the code that processes the returned results.
A simple and effective approach is to maintain a counter that tracks number of finished Ajax calls.
function processResults (result1, result2) {
// process returns from two parallel ajax calls
}
var count = 0, result1, result2;
$.ajax({
url: 'service/endpoint1',
success: function(data1) {
result1 = data1;
count ++;
if (count == 2) {
processResults (result1, result2);
}
},
error:function(error1) {
// handle error from the first ajax call
}
});
$.ajax({
url: 'service/endpoint2',
success: function(data2) {
result2 = data2;
count ++;
if (count == 2) {
processResults (result1, result2);
}
},
error:function(error2) {
// handle error from the second ajax call
}
});
In the above example, we will make sure we get return from both Ajax calls before executing the processResults function. Just as the case for serial calls, you will definitely prefer chaining over using callbacks or explicitly managing the state of Ajax calls.
Now let us talk about the dynamic chaining that Cloud CMS introduces.
Cloud CMS is a content platform that help you build cloud-connected applications. When you build a Cloud CMS application, your application will interact with Cloud CMS platform through its REST APIs. In order to provide pleasant programming experience to developers, it is critical for Cloud CMS to have a driver that times or coordinates multiple REST/ajax calls in a very easy manner. That is why Cloud CMS provides a JavaScript Driver which comes with support for dynamic chaining.
The driver provides a Chain class that allow you to instantiate, extend or end a chain. A chain can carry an underlying proxied object which performs proxied Ajax calls to the REST services that Cloud CMS provides.
A chain can also be extended with a different proxied object if needed. The driver provides a list of “Chainable” classes that can be instantiated as the proxied objects for the chaining. For example, the Repository class will provide methods that deals with repository related or its sub-level objects related operations such as updating repository, creating new branch etc.
So when we make a call to
repository.readBranch('master');
it will make a proxied GET call to retrieve details of the master branch of the repository.
The call will look like
http://localhost/proxy/repositories/aaa718b9bd29f76f443b/branches/master?metadata=true&full=true&cb=1338678288323
where aaa718b9bd29f76f443b is the repository id.
Please note that Cloud CMS JavaScript driver doesn’t reinvent the way to manage Ajax calls.
Under the hood, it still uses the callback approach to time the ajax calls and use the counter approach to keep track the parallel calls. It just provides utilities and simple APIs that shield developers away from dealing with those details.
Let us start with a simple example that manage the life cycle of a simple chain which doesn’t deal with Ajax call.
// Create a new chain
Chain().then(function() {
var data = 'Marry ';
// Extend the chain
this.subchain().then(function() {
data += 'has a little ';
}).then(function() {
data += 'lamb.';
// We should have "Marry has a little lamb." at the end the chain.
});
});
Now let us take a step further and try to use a chain with proxied objects to create a new node under the master branch of a given repository.
new Gitana({
"clientId": "SOMEID",
"clientSecret": "SOMESECRET"
}).authenticate({
"username": "SOMEUSERNAME",
"password": "SOMEPASSWORD"
}).readRepository("SOMEREPOSITORYID").readBranch('master').createNode().then(function() {
// we have successfully created a new node
});
In the above example, it first creates a new client with correct credentials. Once it is authenticated, it creates a new chain with a proxied Platform object. The Platform object will then be used to read the repository with the given ID.
The chain will then be extended and the underlying proxied object will be switched to a proxied Repository object populated from the previous ajax call response. It will keeps the chain going by reading the master branch (switching the proxied object to Branch) and then creating a new node under it (switching the proxied object to Node).
If we want to extend the example to create three new nodes in serial, the example will look like
new Gitana({
"clientId": "SOMEID",
"clientSecret": "SOMESECRET"
}).authenticate({
"username": "SOMEUSERNAME",
"password": "SOMEPASSWORD"
}).readRepository("SOMEREPOSITORYID").readBranch('master').then(function() {
this.createNode();
this.createNode();
this.createNode();
this.then(function() {
// we have successfully created three new nodes by making serial calls
});
});
Now if we want to create new nodes in parallel, we can do something like this
new Gitana({
"clientId": "SOMEID",
"clientSecret": "SOMESECRET"
}).authenticate({
"username": "SOMEUSERNAME",
"password": "SOMEPASSWORD"
}).readRepository("SOMEREPOSITORYID").readBranch('master').then(function() {
var f = function(){
this.createNode();
};
this.then([f,f,f]).then(function() {
// we have successfully created three new nodes by making parallel calls
});
});
As you can see from the above examples, the driver significantly simplifies the code with chaining. It makes the code easier to read and less error prone. It follows the human’s nature feeling of synchronous chaining while dealing with actual asynchronous ajax calls under the hood.
Before we wrap up this blog, let us take a look a more complex example that does mix of serial ajax calls and parallel calls. It will also show how to manually switch the underlying proxied object by using subchain method.
new Gitana({
"clientId": "SOMEID",
"clientSecret": "SOMESECRET"
}).authenticate({
"username": "SOMEUSERNAME",
"password": "SOMEPASSWORD"
}).readRepository("SOMEREPOSITORYID").readBranch('master').then(function() {
var node1, node2, node3;
var f1 = function() {
this.createNode().then(function() {
node1 = this;
});
};
var f2 = function() {
this.createNode().then(function() {
node2 = this;
});
};
var f3 = function() {
this.createNode().then(function() {
node3 = this;
});
};
this.then([f1,f2,f3]).then(function() {
// we have successfully created three new nodes by making parallel calls
// we now associate node2 to node1 ( node1 ==> node2)
this.subchain(node1).associate(node2).then(function() {
// we have successfully associated node2 with node1
});
// At this point, node2 has already been associated with node1
// we then associate node2 to node3 ( node3 ==> node2)
this.subchain(node2).associateOf(node3).then(function() {
// we have successfully associated node2 with node3
// The chain will end at this point.
});
});
});
For live chaining examples, please check out our online JavaScript Samples.