The JayData core supports deferred results, aka promises in all the cases where an async operation is involved. The promise implementation is pluggable so you can chose to work with jQuery.deferred or Q. The client interface is the same regardless of which implementation is selected. Also, you can write you own promise implementation and have it used by JayData (an expert scenario this post will not cover).
Please note that while this example uses the OData provider and the Northwind sample database (published with a WCF Data Service), promise interfaces work across all JayData providers: mongoDB, indexedDB, sqLite or YQL just to name a few.
Prepare your environment
Promise implementations are introduced to the JayData core with the inclusion of their respective adapters in your HTML5 page or JavaScript application.
jQuery.deferred
If you are about to use jQuery.deferred, your script block should look like this:
1 2 3 4 5 6 7 |
<span class="kwrd"><</span><span class="html">script</span> <span class="attr">src</span><span class="kwrd">="Scripts/jquery-1.7.2.min.js"</span><span class="kwrd">></</span><span class="html">script</span><span class="kwrd">></span> <script src=<span class="str">"Scripts/datajs-1.0.3.js"</span>></script> <script src=<span class="str">"Scripts/jaydata.js"</span>></script> <script src=<span class="str">"Scripts/jaydataproviders/oDataProvider.js"</span>></script> <!-- <span class="kwrd">this</span> <span class="kwrd">is</span> the jQuery.deferred adapter --> <script src=<span class="str">"Scripts/jaydatamodules/deferred.js"</span>><span class="kwrd"></</span><span class="html">script</span><span class="kwrd">></span> |
Q library on the client
Q is a cool, cross layer deferred framework. You can grab it from github. To use Q in the client side your script block should look like this:
1 2 3 4 5 6 |
<script src="Scripts/datajs-1.0.3.js"></script> <script src="Scripts/q.js"></script> <script src="Scripts/jaydata.js"></script> <script src="Scripts/jaydataproviders/oDataProvider.js"></script> <!-- this is the Q deferred adapter --> <script src="Scripts/jaydatamodules/qDeferred.js"></script> |
Q library on the server with NodeJS
You can get Q with npm. Then:
1 2 3 |
require('odata'); require('jaydata'); require('q'); |
Working with promises
Now that you have set it up, you can be promised!
Using promise.then and promise.fail instead of result callbacks
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$data.service(<span class="str">'/Northwind.svc'</span>, <span class="kwrd">function</span> (contextFactory, contextType) { contextFactory() .Categories .toArray() .then(<span class="kwrd">function</span> (items) { console.log(<span class="str">"items received:"</span>, items.length); console.log(items); }) .fail(<span class="kwrd">function</span> (err) { console.log(<span class="str">"Errors occured:"</span>, err); }); }); |
The result
Note that in this simple use case this is equivalent with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$data.service(<span class="str">'/Northwind.svc'</span>, <span class="kwrd">function</span> (contextFactory, contextType) { contextFactory() .Categories .toArray({ success: <span class="kwrd">function</span> (items) { console.log(<span class="str">"items received:"</span>, items.length); console.log(items); }, error: <span class="kwrd">function</span> (err) { console.log(<span class="str">"Errors occured:"</span>, err); } }); }); |
The big difference comes in when you have to work with chained operations: multiple things have to be done, and one operation depends on the result of a previous one. While you can chain operations with success/error callbacks too, the result will be a deep callback chain that is very hard to maintain. Promises help you flatten you code (nesting wise) and make up to a more readable code that is more easy to support on the long run.
Chaining async operations
The following code gets the first two categories (based on default ordering) and then gets their respective products.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$data.service(<span class="str">'/Northwind.svc'</span>, <span class="kwrd">function</span> (contextFactory, contextType) { <span class="kwrd">var</span> context = contextFactory(); context .Categories .map(<span class="str">"it.Category_ID"</span>) .take(2) .toArray() .then(<span class="kwrd">function</span> (categoryIds) { console.log(<span class="str">"categories received:"</span>, categoryIds); <span class="kwrd">return</span> context .Products .filter(<span class="str">"it.Category_ID in this.ids"</span>, { ids: categoryIds }) .toArray(); }) .then(<span class="kwrd">function</span> (products) { console.log(<span class="str">"Products received"</span>); console.log(products); }); }); |
The Result
Waiting for the completion of two or more async operations
The following code gets the list of all supliers while creating a new product line in the database. We’ll get notified when all of these happened.
1 2 3 4 5 6 7 8 9 |
$data.service(<span class="str">'/Northwind.svc'</span>, <span class="kwrd">function</span> (contextFactory, contextType) { <span class="kwrd">var</span> context = contextFactory(); context.Products.add({ Product_Name: <span class="str">'My JavaScript Product'</span> }); Q.all([context.Suppliers.toArray(), context.saveChanges()]) .then(<span class="kwrd">function</span> () { console.log(<span class="str">"Multiple operations completed:"</span>, arguments); }); }); |