web staff / log : promises
authorBill Erickson <berick@esilibrary.com>
Wed, 4 Dec 2013 22:12:16 +0000 (17:12 -0500)
committerBill Erickson <berick@esilibrary.com>
Wed, 4 Dec 2013 22:12:16 +0000 (17:12 -0500)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
web-staff-log.txt

index 169ca9e..ca679bb 100644 (file)
@@ -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 will magically display in the 
+    page after the async call completes -->
+<div>Username: {{patron.usrname()}}</div>
+-----------------------------------------------------------------------------
+
+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...
 ----------------