Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($httpProvider): add 'useLegacyPromiseExtensions' configuration
Browse files Browse the repository at this point in the history
The legacy methods, `success` and `error`, have been deprecated.

Set this to `false` to cause `$http` to throw an error if these methods are
used in the application.

For now it defaults to `true`. In a future release we will remove these
methods altogether.

DEPRECATION NOTICE:

The legacy methods 'success' and 'error' on promises returned by $http
are now deprecated.

Closes #12112
Closes #10508
  • Loading branch information
realityking authored and petebacondarwin committed Aug 1, 2015
1 parent 7b8a16b commit a8f7e9c
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 50 deletions.
45 changes: 45 additions & 0 deletions docs/content/error/$http/legacy.ngdoc
@@ -0,0 +1,45 @@
@ngdoc error
@name $http:legacy
@fullName The `success` and `error` methods on the promise returned from `$http` have been disabled.
@description

This error occurs when the legacy promise extensions (`success` and `error`)
{@link $httpProvider#useLegacyPromiseExtensions legacy `$http` promise extensions} have been disabled.

To resolve this error, either turn on the legacy extensions by adding
`$httpProvider.useLegacyPromiseExtensions(true);` to your application's configuration; or refactor you
use of `$http` to use `.then()` rather than `.success()` and `.error()`.

For example if you code looked like this:

```js
// Simple GET request example :
$http.get('/someUrl').
success(function(data, status, headers, config) {
// This callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
```

then you would change it to look like:

```js
// Simple GET request example :
$http.get('/someUrl').
then(function(response) {
// (The response object contains the data, status, headers and config properties)
// This callback will be called asynchronously
// when the response is available.
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
```

For more information, see the
{@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`}
documentation.
130 changes: 80 additions & 50 deletions src/ng/http.js
Expand Up @@ -8,6 +8,12 @@ var JSON_ENDS = {
'{': /}$/
};
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
var $httpMinErr = minErr('$http');
var $httpMinErrLegacyFn = function(method) {
return function() {
throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method);
};
};

function serializeValue(v) {
if (isObject(v)) {
Expand Down Expand Up @@ -330,6 +336,30 @@ function $HttpProvider() {
return useApplyAsync;
};

var useLegacyPromse = true;
/**
* @ngdoc method
* @name $httpProvider#useLegacyPromiseExtensions
* @description
*
* Configure `$http` service to return promises without the shorthand methods `success` and `error`.
* This should be used to make sure that applications work without these methods.
*
* Defaults to false. If no value is specified, returns the current configured value.
*
* @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods.
*
* @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
* otherwise, returns the current configured value.
**/
this.useLegacyPromiseExtensions = function(value) {
if (isDefined(value)) {
useLegacyPromse = !!value;
return this;
}
return useLegacyPromse;
};

/**
* @ngdoc property
* @name $httpProvider#interceptors
Expand Down Expand Up @@ -396,17 +426,15 @@ function $HttpProvider() {
*
* ## General usage
* The `$http` service is a function which takes a single argument — a configuration object —
* that is used to generate an HTTP request and returns a {@link ng.$q promise}
* with two $http specific methods: `success` and `error`.
* that is used to generate an HTTP request and returns a {@link ng.$q promise}.
*
* ```js
* // Simple GET request example :
* $http.get('/someUrl').
* success(function(data, status, headers, config) {
* then(function(response) {
* // this callback will be called asynchronously
* // when the response is available
* }).
* error(function(data, status, headers, config) {
* }, function(response) {

This comment has been minimized.

Copy link
@gkalpak

gkalpak Aug 13, 2015

Member

Wouldn't it be preferable to use catch() instead (here and below) ?

This comment has been minimized.

Copy link
@petebacondarwin

petebacondarwin Aug 14, 2015

Member

@gkalpak I don't think so. These examples are demonstrating how to react to both a success and error response.

This comment has been minimized.

Copy link
@gkalpak

gkalpak Aug 17, 2015

Member

I think it is a better practice to use catch() in order to also catch errors in the success callback.
It doesn't make any difference in these simple examples, but I believe it is better to promote good practices anyway:

$http.
  get(...).
  then(successCallback).
  catch(errorCallback);

Not feeling too strongly about it, just saying... :)

This comment has been minimized.

Copy link
@petebacondarwin

petebacondarwin Aug 21, 2015

Member

In that case you should have both, since I expect that reacting to an exception in the success handler would need different behaviour to reacting to a failed HTTP request:

$http.
  get(...).
  then(successCallback, failCallback).
  catch(errorCallback);

This also means that we can catch errors in the failCallback too!

This comment has been minimized.

Copy link
@gkalpak

gkalpak Aug 21, 2015

Member

This also means that we can catch errors in the failCallback too!

If one has errors in their failCallback, I think they are beyond salvation 😛

You could do it with separate failCallback and errorCallback. In practise I usually use the errorCacllback only, where I log the error and display a notification to the user (using either the failed response error info or a generic error message in case of an unexpected error in the successCallback).

But (theoretically speaking 😉) your version is more "thorough".

* // called asynchronously if an error occurs
* // or server returns response with an error status.
* });
Expand All @@ -415,21 +443,23 @@ function $HttpProvider() {
* ```js
* // Simple POST request example (passing data) :
* $http.post('/someUrl', {msg:'hello word!'}).
* success(function(data, status, headers, config) {
* then(function(response) {
* // this callback will be called asynchronously
* // when the response is available
* }).
* error(function(data, status, headers, config) {
* }, function(response) {
* // called asynchronously if an error occurs
* // or server returns response with an error status.
* });
* ```
*
* The response object has these properties:
*
* Since the returned value of calling the $http function is a `promise`, you can also use
* the `then` method to register callbacks, and these callbacks will receive a single argument –
* an object representing the response. See the API signature and type info below for more
* details.
* - **data** – `{string|Object}` – The response body transformed with the transform
* functions.
* - **status** – `{number}` – HTTP status code of the response.
* - **headers** – `{function([headerName])}` – Header getter function.
* - **config** – `{Object}` – The configuration object that was used to generate the request.
* - **statusText** – `{string}` – HTTP status text of the response.
*
* A response status code between 200 and 299 is considered a success status and
* will result in the success callback being called. Note that if the response is a redirect,
Expand All @@ -453,8 +483,8 @@ function $HttpProvider() {
* request data must be passed in for POST/PUT requests.
*
* ```js
* $http.get('/someUrl').success(successCallback);
* $http.post('/someUrl', data).success(successCallback);
* $http.get('/someUrl').then(successCallback);
* $http.post('/someUrl', data).then(successCallback);
* ```
*
* Complete list of shortcut methods:
Expand All @@ -468,6 +498,14 @@ function $HttpProvider() {
* - {@link ng.$http#patch $http.patch}
*
*
* ## Deprecation Notice
* <div class="alert alert-danger">
* The `$http` legacy promise methods `success` and `error` have been deprecated.
* Use the standard `then` method instead.
* If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to
* `false` then these methods will throw {@link $http:legacy `$http/legacy`} error.
* </div>
*
* ## Setting HTTP Headers
*
* The $http service will automatically add certain HTTP headers to all requests. These defaults
Expand Down Expand Up @@ -511,7 +549,7 @@ function $HttpProvider() {
* data: { test: 'test' }
* }
*
* $http(req).success(function(){...}).error(function(){...});
* $http(req).then(function(){...}, function(){...});
* ```
*
* ## Transforming Requests and Responses
Expand Down Expand Up @@ -743,7 +781,6 @@ function $HttpProvider() {
* In order to prevent collisions in environments where multiple Angular apps share the
* same domain or subdomain, we recommend that each application uses unique cookie name.
*
*
* @param {object} config Object describing the request to be made and how it should be
* processed. The object has following properties:
*
Expand Down Expand Up @@ -788,20 +825,9 @@ function $HttpProvider() {
* - **responseType** - `{string}` - see
* [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
*
* @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
* standard `then` method and two http specific methods: `success` and `error`. The `then`
* method takes two arguments a success and an error callback which will be called with a
* response object. The `success` and `error` methods take a single argument - a function that
* will be called when the request succeeds or fails respectively. The arguments passed into
* these functions are destructured representation of the response object passed into the
* `then` method. The response object has these properties:
* @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
* when the request succeeds or fails.
*
* - **data** – `{string|Object}` – The response body transformed with the transform
* functions.
* - **status** – `{number}` – HTTP status code of the response.
* - **headers** – `{function([headerName])}` – Header getter function.
* - **config** – `{Object}` – The configuration object that was used to generate the request.
* - **statusText** – `{string}` – HTTP status text of the response.
*
* @property {Array.<Object>} pendingRequests Array of config objects for currently pending
* requests. This is primarily meant to be used for debugging purposes.
Expand Down Expand Up @@ -843,13 +869,12 @@ function $HttpProvider() {
$scope.response = null;
$http({method: $scope.method, url: $scope.url, cache: $templateCache}).
success(function(data, status) {
$scope.status = status;
$scope.data = data;
}).
error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
then(function(response) {
$scope.status = response.status;
$scope.data = response.data;
}, function(response) {
$scope.data = response.data || "Request failed";
$scope.status = response.status;
});
};
Expand Down Expand Up @@ -954,23 +979,28 @@ function $HttpProvider() {
promise = promise.then(thenFn, rejectFn);
}

promise.success = function(fn) {
assertArgFn(fn, 'fn');
if (useLegacyPromse) {
promise.success = function(fn) {
assertArgFn(fn, 'fn');

promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};

promise.error = function(fn) {
assertArgFn(fn, 'fn');
promise.error = function(fn) {
assertArgFn(fn, 'fn');

promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
} else {
promise.success = $httpMinErrLegacyFn('success');
promise.error = $httpMinErrLegacyFn('error');
}

return promise;

Expand Down
30 changes: 30 additions & 0 deletions test/ng/httpSpec.js
Expand Up @@ -1975,6 +1975,36 @@ describe('$http with $applyAsync', function() {
});
});

describe('$http without useLegacyPromiseExtensions', function() {
var $httpBackend, $http;
beforeEach(module(function($httpProvider) {
$httpProvider.useLegacyPromiseExtensions(false);
}, provideLog));

beforeEach(inject(['$httpBackend', '$http', '$rootScope', function($hb, $h, $rs) {
$httpBackend = $hb;
$http = $h;
}]));

it('should throw when the success or error methods are called if useLegacyPromiseExtensions is false', function() {
$httpBackend.expect('GET', '/url').respond('');
var promise = $http({url: '/url'});

function callSucess() {
promise.success();
}

function callError() {
promise.error();
}

expect(callSucess).toThrowMinErr(
'$http', 'legacy', 'The method `success` on the promise returned from `$http` has been disabled.');
expect(callError).toThrowMinErr(
'$http', 'legacy', 'The method `error` on the promise returned from `$http` has been disabled.');
});
});

describe('$http param serializers', function() {

var defSer, jqrSer;
Expand Down

0 comments on commit a8f7e9c

Please sign in to comment.