Skip to main content
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Hugo Sombreireiro
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Hugo Sombreireiro

Aborting AJAX Requests Using $http And AngularJS

By
Published in Comments (18)

If you're coming from a jQuery background, you're probably used to calling .abort() on the AJAX (Asynchronous JavaScript and XML) response object (jqXHR). In AngularJS, things are a little bit more complicated. AngularJS don't expose the .abort() method; but, it does provide a way to abort an active AJAX request. It lets you define a promise object that will abort the underlying request if and when the promise value is resolved.

In AngularJS, you use the $http service to make AJAX requests. When you initiate the AJAX request, you have to provide $http with a configuration object. One of the configuration options is "timeout". This option takes either a numeric value (milliseconds) or a promise. If the promise is resolved before the AJAX request has completed, AngularJS will call .abort() on the underlying XMLHttpRequest object.

NOTE: The timeout property only supports milliseconds and promises as of v1.2. Prior to that, the timeout option only supported milliseconds; meaning, there was no way to manually abort the underlying XMLHttpRequest object, even in v1.0.8.

As of late, I've been trying to create better encapsulation of the data persistence and retrieval mechanisms. This makes the matter a bit more complex because I don't want the calling context to have to worry about the $http service or about wiring up the timeout. To accomplish this, I have to create the deferred object and then inject it into the data response promise before I return it to the calling context. This way, the calling context can just call .abort() on the promise and the AJAX request will be aborted.

To see what I mean, take a look at the following code. The API in this demo (not shown) has a 5-second sleep() call to give us enough time to abort the request.

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />

	<title>
		Aborting AJAX Requests Using $http And AngularJS
	</title>

	<style type="text/css">

		a[ ng-click ] {
			color: red ;
			cursor: pointer ;
			text-decoration: underline ;
		}

	</style>
</head>
<body ng-controller="DemoController">

	<h1>
		Aborting AJAX Requests Using $http And AngularJS
	</h1>

	<p>
		<a ng-click="loadData()">Load Data</a> -
		<a ng-click="abortRequest()">Abort Request</a>
	</p>

	<!-- Show when data is loading. -->
	<p ng-if="isLoading">
		<em>Loading...</em>
	</p>

	<!-- Show when data has finished loading. -->
	<ul ng-if="! isLoading">
		<li ng-repeat="friend in friends">

			{{ friend.name }}

		</li>
	</ul>


	<!-- Initialize scripts. -->
	<script type="text/javascript" src="../jquery/jquery-2.1.0.min.js"></script>
	<script type="text/javascript" src="../angularjs/angular-1.2.4.min.js"></script>
	<script type="text/javascript">

		// Define the module for our AngularJS application.
		var app = angular.module( "Demo", [] );


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I control the main demo.
		app.controller(
			"DemoController",
			function( $scope, friendService ) {

				// I determine if remote data is currently being loaded.
				$scope.isLoading = false;

				// I contain the data that we wan to render.
				$scope.friends = [];

				// I hold the handle on the current request for data. Since we want to
				// be able to abort the request, mid-stream, we need to hold onto the
				// request which will have the .abort() method on it.
				var requestForFriends = null;


				// ---
				// PUBLIC METHODS.
				// ---


				// I abort the current request (if its running).
				$scope.abortRequest = function() {

					return( requestForFriends && requestForFriends.abort() );

				};


				// I load the remote data for the view.
				$scope.loadData = function() {

					// Flag the data is currently being loaded.
					$scope.isLoading = true;
					$scope.friends = [];

					// Make a request for data. Note that we are saving a reference to
					// this response rather than just piping it directly into a .then()
					// call. This is because we need to be able to access the .abort()
					// method on the request and we'll lose that original reference after
					// we call the .then() method.
					( requestForFriends = friendService.getFriends() ).then(
						function( newFriends ) {

							// Flag the data as loaded.
							$scope.isLoading = false;
							$scope.friends = newFriends;

						},
						function( errorMessage ) {

							// Flag the data as loaded (or rather, done trying to load). loading).
							$scope.isLoading = false;

							console.warn( "Request for friends was rejected." );
							console.info( "Error:", errorMessage );

						}
					);

				};

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I am the friend repository.
		app.service(
			"friendService",
			function( $http, $q ) {

				// I get the list of friends from the remote server.
				function getFriends() {

					// The timeout property of the http request takes a deferred value
					// that will abort the underying AJAX request if / when the deferred
					// value is resolved.
					var deferredAbort = $q.defer();

					// Initiate the AJAX request.
					var request = $http({
						method: "get",
						url: "./api/friends.cfm",
						timeout: deferredAbort.promise
					});

					// Rather than returning the http-promise object, we want to pipe it
					// through another promise so that we can "unwrap" the response
					// without letting the http-transport mechansim leak out of the
					// service layer.
					var promise = request.then(
						function( response ) {

							return( response.data );

						},
						function( response ) {

							return( $q.reject( "Something went wrong" ) );

						}
					);

					// Now that we have the promise that we're going to return to the
					// calling context, let's augment it with the abort method. Since
					// the $http service uses a deferred value for the timeout, then
					// all we have to do here is resolve the value and AngularJS will
					// abort the underlying AJAX request.
					promise.abort = function() {

						deferredAbort.resolve();

					};

					// Since we're creating functions and passing them out of scope,
					// we're creating object references that may be hard to garbage
					// collect. As such, we can perform some clean-up once we know
					// that the requests has finished.
					promise.finally(
						function() {

							console.info( "Cleaning up object references." );

							promise.abort = angular.noop;

							deferredAbort = request = promise = null;

						}
					);

					return( promise );

				}


				// Return the public API.
				return({
					getFriends: getFriends
				});

			}
		);

	</script>

</body>
</html>

As you can see, after I've created the data-promise (as opposed to the AJAX-promise), I then augment it with an abort() method which will resolve the timeout.

The one big caveat to this approach is that if the calling context tries to invoke the .then() method on the resultant promise, the calling context will lose a handle on the .abort() method as it will be lost in the promise chain. This is why I am purposefully saving a reference to the primary promise before I hook into the resolution and rejection callbacks.

Aborting an AJAX request in AngularJS is not as easy as it is in jQuery. But, it's still possible; and, with enough encapsulation, you can still provide a simple interface for your data consumers. As a final note, I should also point out that $resource modules, as of v1.2, also provides a timeout option (though I have not yet tired it).

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

Reader Comments

15,880 Comments

@Chief,

Ah, thanks for the clarification! I thought it was 1.2 because that was the first instance I found of it in the code. I'm still learning how to get around various branches in GitHub.

1 Comments

First off, I found your blog looking for some info about the $resource (Service? Factory? Provider? :P ) and found the rest of the posts so, so very good, that I already read a bunch of them.

But in this one, you lost me. I know it's because I certainly have no idea about how exactly promises work (Still trying to convince my mind that what the code does is *not* magic of the black kind).

Was wondering if you could point me to a good resource on the subject? (Promises, that is. For Angular I'd be subscribing to your blog!)

BTW, your post on "Using RESTful Controllers In An AngularJS Resource", if no exactly what I was looking for, is one of the *best* posts I've read on Angular. Thank you very much (You're not planning on writing a book, are you?)

1 Comments

Hi, I'm really new to AngularJS (coming from jQuery).
Your post was exactly what I searched for - thanks a lot! :)

The only thing on this solution I still got problems with is, that every time I cancel the request I got the error messages (Line 110-116).

Do you have a nice approach to distinguish between "cancel-errors" and "real" request errors (for ex. when server is not responding)?

Thanks,
Pascal

1 Comments

When I used HTTP get method, it works but it seems that it doesn't work on post method. But I will need to cancel post request, do you have any suggestions? You can take a look at my demonstration:

http://plnkr.co/edit/gkvxRj

1 Comments

Hi , How do we cancel a particular request, when there are five request is in process.

For Example: If I am requesting 5 HTTP request, I want to cancel the third request , So how can I achieve this...

Thanks
Jegan

1 Comments

Tried to implement this to cancel all pending requests on route change. All Pending requests are getting canceled But, still success callback (provided in controller) executing and console displaying errors (response is undefined) when we change route while there are some requests in pending status. (Those requests got canceled).

If I am doing promise.reject("Route Rejection"); then it calls error callback but requests does not gets aborted.

1 Comments

Hi Ben, I have a web application with heavy server calls. By heavy, I mean that each request, take at least 15-20 seconds. User is permitted to switch between different menu items even when one is not loaded completely. For some impatient users, who can not wait for the loading bar to stop, the application becomes unresponsive after few clicks.

As a solution to this, I was thinking to cancel the request as soon as user switches from one menu item to another (on scope destroy). But this does not seem to work for me. The requests are aborted successfully. But still the next request takes the same amount of time if the first request was not aborted. Would you have any idea how to deal with this?

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