From 344a8c1951c8de434b253463b10cc04de9fe3895 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 4 Dec 2013 17:12:16 -0500 Subject: [PATCH] web staff / log : promises Signed-off-by: Bill Erickson --- web-staff-log.txt | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/web-staff-log.txt b/web-staff-log.txt index 169ca9edd6..ca679bb67c 100644 --- a/web-staff-log.txt +++ b/web-staff-log.txt @@ -627,6 +627,164 @@ required to have them installed. Finally, it needs a better name than "add-on" (Add-On 2.0!) and it needs developers/sponsors, and all that fun stuff. +2013-12-04 Promises in AngularJS +-------------------------------- + +One of my favorite new-ish tools in the Javascript world are called +"Promises". + +**** +If a function cannot return a value or throw an exception without +blocking, it can return a promise instead. A promise is an object that +represents the return value or the thrown exception that the function +may eventually provide. A promise can also be used as a proxy for a +remote object to overcome latency. -- https://github.com/kriskowal/q +**** + +Typically, with asynchronous network calls, the caller will make the +call and pass in a callback function, which is invoked when the async +call returns. This works well enough for single calls, but it does not +scale well. Promises allow us to manage collections of asynchronous +calls much more elegantly. + +Here's a quick example: + +[source,js] +----------------------------------------------------------------------------- +// non-promise api call +request(service, method, params, {oncomplete : callback}); + +// promise-based call +request(service, method, params).then(callback); +----------------------------------------------------------------------------- + +At first, the difference seems trivial, but it becomes more +pronounced as more requests are added. + +Batch Parallel Request Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +[source,js] +----------------------------------------------------------------------------- +// non-promise batch +// send three, parallel requests and track the +// responses to see when all are done +var expected = 3; +var checkComplete = function() { + if (--expected == 0) { // decrement w/ each response + // all responses received, move on to other things + } +} +request(s, m, p, function(r) { /*do stuff*/; checkComplete() }); +request(s, m, p, function(r) { /*do stuff*/; checkComplete() }); +request(s, m, p, function(r) { /*do stuff*/; checkComplete() }); + +// promise-based batch +var promises = []; +promises.push(request(s, m, p).then(function(r) {/*do stuff*/})); +promises.push(request(s, m, p).then(function(r) {/*do stuff*/})); +promises.push(request(s, m, p).then(function(r) {/*do stuff*/})); +$q.all(promises).then( + function() { + // all responses received, move on to other things + } +); + +// $q.all() takes a collection of promise objects and returns a new +// promise which is resolved when all of the embedded promises are resolved +----------------------------------------------------------------------------- + +Not only is the code a little cleaner, but it flows in the same +direction as execution. In other words, checkComplete(), which is the +last-executed code in the non-promise example is defined before the +requests are sent. This creates a spaghetti effect in the code, where +you have to jump around to follow the logic. Not so in the promise +example. It flows top to bottom. + +Attaching to Promises +~~~~~~~~~~~~~~~~~~~~~ + +Another neat trick you can do w/ promises is create a promise to +represent a long-running task. When clients ask for the result of the +task and the task is not yet complete, the promise can be returned to +any number of clients. + +[source,js] +----------------------------------------------------------------------------- +function longTask() { + if (this.promise) { + return this.promise; + } else { + this.promise = performAsyncTask(); + return this.promise; + } +} + +// the first call kicks off the long-running process +longTask().then(/*func*/); + +// subsequent calls from other locations simply pick up the existing promise + +// different location in the code +longTask().then(/*func*/); + +// different location in the code +longTask().then(/*func*/); + +// different location in the code +longTask().then(/*func*/); + +// when longTask() finally completes, all promise handlers are run +// Also, if longTask() was *already* complete, the promise handler +// will be immediately run. +----------------------------------------------------------------------------- + +This structure is used to great effect in the prototype page +initialization code. Independent controllers that all want to ensure +the client has authenticated and retrieved necessary data will call the +startup code, the first will kick it off, and the others will latch on +the outstanding promise. + +As a final bonus to using promises within Angular, promise resolution +causes another $digest() run in Angular, which causes templates to get +updated. + +[source,js] +----------------------------------------------------------------------------- +egNet.request( + 'open-ils.actor', + 'open-ils.actor.user.retrieve', + egAuth.token(), userId +).then( + function(user) { + $scope.patron = patron; + } +); +----------------------------------------------------------------------------- +[source,html] +----------------------------------------------------------------------------- + +
Username: {{patron.usrname()}}
+----------------------------------------------------------------------------- + +Promises also support reject() and notify() handlers for failed requests +and intermediate messages. In my egNet module, I'm leveraging notify() +for streaming responses. + +[source,js] +----------------------------------------------------------------------------- +egNet.request(service, method, params).then( + function(finalResp) { console.log('all done') }, + function() { console.error('request failed!') }, + function(resp) { console.log('got a response ' + resp) } +); +// The egNet module has additional examples and docs. +----------------------------------------------------------------------------- + +For the full technical rundown, see also +http://docs.angularjs.org/api/ng.$q[Angular $q Docs]. + Future Topics... ---------------- -- 2.11.0