Skip to main content
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Jeff Kunkel
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Jeff Kunkel

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

By
Published in

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel