Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Chris Peters
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Chris Peters@cf_chrispeters )

Passing $q Defer Methods Around As Naked Function References In AngularJS

By Ben Nadel on

A few years ago, I noticed that the Deferred object methods, in jQuery, could be passed around as naked function references. Meaning, instead of passing around a closure that acted upon a deferred instance, I could simply pass around a reference to the "resolve" function. The AngularJS documentation doesn't mention this as a feature; but, I wanted to see if they've implemented the $q service with the same techniques in mind.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

Normally, if you have an object and you want to execute a method on that object from within a different context, you have two options. Either you can create a function that binds the method invocation to the object context, using something like the .bind() method in AngularJS:

  • passOutOfScope( angular.bind( objectInstance, methodReference ) );

... or, you can pass a function that uses the lexical scope to close-over the method you want to invoke (ie, create a closure):

  • passOutOfScope( function closure() {
  •  
  • return( objectInstance.methodReference() );
  •  
  • } );

In jQuery, the Deferred library is built using closures (the latter example), which lexically binds the deferred instance to the given methods. This is what allows us to pass the jQuery Deferred methods around as naked function references.

Let's look at some AngularJS code to see if the same is true for the the $q service. In the following exploration, we're going to create a Defer() instance and then pass various methods to the $timeout() service as naked function references.

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Passing $q Defer Methods Around In AngularJS
  • </title>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Passing $q Defer Methods Around In AngularJS
  • </h1>
  •  
  • <p>
  • <em>Logging execution times in the console.</em>
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.6.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope, $q, $timeout ) {
  •  
  • var deferred = $q.defer();
  •  
  • // Bind the resolution and update of the deferred value.
  • deferred.promise.then(
  • function handleResolve() {
  •  
  • console.log( "Deferred resolved at", time() );
  •  
  • },
  • null,
  • function handleNotify() {
  •  
  • console.log( "Deferred notified at", time() );
  •  
  • }
  • );
  •  
  • console.log( "Starting time at", time() );
  •  
  • // Copy the resolve and notify method references. We can pass these
  • // naked functions around with an object-scoping and they will still
  • // work because the deferred instance methods are all implicitly bound
  • // to the deferred object.
  • // --
  • // NOTE: .notify() wasn't available on the $q library until AngularJS 1.2.
  • var resolve = deferred.resolve;
  • var reject = deferred.reject;
  • var notify = deferred.notify;
  •  
  • // Pass the naked methods into the timeout service so we can see them
  • // execute properly without scoping.
  • $timeout( notify, 1000 );
  • $timeout( notify, 2000 );
  • $timeout( notify, 3000 );
  • $timeout( resolve, 4000 );
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I return the current time string.
  • function time() {
  •  
  • return( ( new Date() ).toTimeString() );
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the .resolve() and .notify() methods are being passed to the $timeout() service as unscoped values. And yet, when we run the code, we get the following console output:

Starting time at 09:46:42 GMT-0500 (EST)
Deferred notified at 09:46:43 GMT-0500 (EST)
Deferred notified at 09:46:44 GMT-0500 (EST)
Deferred notified at 09:46:45 GMT-0500 (EST)
Deferred resolved at 09:46:46 GMT-0500 (EST)

Clearly, the naked function references were acting on the appropriate deferred instance; otherwise, our promise-bindings would not have been invoked.

Taking a look at the AngularJS source code, it looks like AngularJS 1.3 uses the .bind() approach (using an internal function called simpleBind()); but, it looks as if AngularJS 1.2 and earlier used the lexical scoping approach, letting the various resolve() and reject() methods "close over" the deferred instance reference.

Regardless of the underlying implementation, it's nice to know that the $q Defer() methods, in AngularJS, can be passed around as naked function references. This can greatly simplify code that needs manage promises across multiple calling contexts.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.