This repository has been archived by the owner on Apr 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(http): support request/response promise chaining
myApp.factory('myAroundInterceptor', function($rootScope, $timeout) { return function(configPromise, responsePromise) { return { request: configPromise.then(function(config) { return config }); response: responsePromise.then(function(response) { return 'ha!'; } }); } myApp.config(function($httpProvider){ $httpProvider.aroundInterceptors.push('myAroundInterceptor'); });
- Loading branch information
Showing
6 changed files
with
480 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -155,20 +155,52 @@ function $HttpProvider() { | |
xsrfHeaderName: 'X-XSRF-TOKEN' | ||
}; | ||
|
||
var providerResponseInterceptors = this.responseInterceptors = []; | ||
/** | ||
* Are order by request. I.E. they are applied in the same order as | ||
* array on request, but revers order on response. | ||
*/ | ||
var interceptorFactories = this.interceptors = []; | ||
/** | ||
* For historical reasons, response interceptors ordered by the order in which | ||
* they are applied to response. (This is in revers to interceptorFactories) | ||
*/ | ||
var responseInterceptorFactories = this.responseInterceptors = []; | ||
|
||
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', | ||
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { | ||
|
||
var defaultCache = $cacheFactory('$http'), | ||
responseInterceptors = []; | ||
var defaultCache = $cacheFactory('$http'); | ||
|
||
forEach(providerResponseInterceptors, function(interceptor) { | ||
responseInterceptors.push( | ||
isString(interceptor) | ||
? $injector.get(interceptor) | ||
: $injector.invoke(interceptor) | ||
); | ||
/** | ||
* Interceptors stored in reverse order. Inner interceptors before outer interceptors. | ||
* The reversal is needed so that we can build up the interception chain around the | ||
* server request. | ||
*/ | ||
var reversedInterceptors = []; | ||
|
||
forEach(interceptorFactories, function(interceptorFactory) { | ||
reversedInterceptors.unshift(isString(interceptorFactory) | ||
? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); | ||
}); | ||
|
||
forEach(responseInterceptorFactories, function(interceptorFactory, index) { | ||
var responseFn = isString(interceptorFactory) | ||
? $injector.get(interceptorFactory) | ||
: $injector.invoke(interceptorFactory); | ||
|
||
/** | ||
* Response interceptors go before "around" interceptors (no real reason, just | ||
* had to pick one.) But they are already revesed, so we can't use unshift, hence | ||
* the splice. | ||
*/ | ||
reversedInterceptors.splice(index, 0, { | ||
response: function(response) { | ||
return responseFn($q.when(response)); | ||
}, | ||
responseError: function(response) { | ||
return responseFn($q.reject(response)); | ||
} | ||
}); | ||
}); | ||
|
||
|
||
|
@@ -310,7 +342,90 @@ function $HttpProvider() { | |
* To skip it, set configuration property `cache` to `false`. | ||
* | ||
* | ||
* # Response interceptors | ||
* # Interceptors | ||
* | ||
* Before you start creating interceptors, be sure to understand the | ||
* {@link ng.$q $q and deferred/promise APIs}. | ||
* | ||
* For purposes of global error handling, authentication or any kind of synchronous or | ||
* asynchronous pre-processing of request or postprocessing of responses, it is desirable to be | ||
* able to intercept requests before they are handed to the server and | ||
* responses before they are handed over to the application code that | ||
* initiated these requests. The interceptors leverage the {@link ng.$q | ||
* promise APIs} to fulfil this need for both synchronous and asynchronous pre-processing. | ||
* | ||
* The interceptors are service factories that are registered with the $httpProvider by | ||
* adding them to the `$httpProvider.interceptors` array. The factory is called and | ||
* injected with dependencies (if specified) and returns the interceptor. | ||
* | ||
* There are two kinds of interceptors (and two kinds of rejection interceptors): | ||
* | ||
* * `request`: interceptors get called with http `config` object. The function is free to modify | ||
* the `config` or create a new one. The function needs to return the `config` directly or as a | ||
* promise. | ||
* * `requestError`: interceptor gets called when a previous interceptor threw an error or resolved | ||
* with a rejection. | ||
* * `response`: interceptors get called with http `response` object. The function is free to modify | ||
* the `response` or create a new one. The function needs to return the `response` directly or as a | ||
* promise. | ||
* * `responseError`: interceptor gets called when a previous interceptor threw an error or resolved | ||
* with a rejection. | ||
* | ||
* | ||
* <pre> | ||
* // register the interceptor as a service | ||
* $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { | ||
* return { | ||
* // optional method | ||
* 'request': function(config) { | ||
* // do something on success | ||
* return config || $q.when(config); | ||
* }, | ||
* | ||
* // optional method | ||
* 'requestError': function(rejection) { | ||
* // do something on error | ||
* if (canRecover(rejection)) { | ||
* return responseOrNewPromise | ||
* } | ||
* return $q.reject(rejection); | ||
* }, | ||
* | ||
* | ||
* | ||
* // optional method | ||
* 'response': function(response) { | ||
* // do something on success | ||
* return response || $q.when(response); | ||
* }, | ||
* | ||
* // optional method | ||
* 'responseError': function(rejection) { | ||
* // do something on error | ||
* if (canRecover(rejection)) { | ||
* return responseOrNewPromise | ||
* } | ||
* return $q.reject(rejection); | ||
* }; | ||
* } | ||
* }); | ||
* | ||
* $httpProvider.interceptors.push('myHttpInterceptor'); | ||
* | ||
* | ||
* // register the interceptor via an anonymous factory | ||
* $httpProvider.interceptors.push(function($q, dependency1, dependency2) { | ||
* return { | ||
* 'request': function(config) { | ||
* // same as above | ||
* }, | ||
* 'response': function(response) { | ||
* // same as above | ||
* } | ||
* }); | ||
* </pre> | ||
* | ||
* # Response interceptors (DEPRECATED) | ||
* | ||
* Before you start creating interceptors, be sure to understand the | ||
* {@link ng.$q $q and deferred/promise APIs}. | ||
|
@@ -526,45 +641,66 @@ function $HttpProvider() { | |
</file> | ||
</example> | ||
*/ | ||
function $http(config) { | ||
function $http(requestConfig) { | ||
var config = { | ||
transformRequest: defaults.transformRequest, | ||
transformResponse: defaults.transformResponse | ||
}; | ||
var headers = {}; | ||
|
||
extend(config, requestConfig); | ||
config.headers = headers; | ||
config.method = uppercase(config.method); | ||
|
||
var xsrfHeader = {}, | ||
xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName, | ||
xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName, | ||
xsrfToken = isSameDomain(config.url, $browser.url()) ? | ||
$browser.cookies()[xsrfCookieName] : undefined; | ||
xsrfHeader[xsrfHeaderName] = xsrfToken; | ||
|
||
var reqTransformFn = config.transformRequest || defaults.transformRequest, | ||
respTransformFn = config.transformResponse || defaults.transformResponse, | ||
defHeaders = defaults.headers, | ||
reqHeaders = extend(xsrfHeader, | ||
defHeaders.common, defHeaders[lowercase(config.method)], config.headers), | ||
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn), | ||
promise; | ||
|
||
// strip content-type if data is undefined | ||
if (isUndefined(config.data)) { | ||
delete reqHeaders['Content-Type']; | ||
} | ||
extend(headers, | ||
defaults.headers.common, | ||
defaults.headers[lowercase(config.method)], | ||
requestConfig.headers); | ||
|
||
if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { | ||
config.withCredentials = defaults.withCredentials; | ||
var xsrfValue = isSameDomain(config.url, $browser.url()) | ||
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] | ||
: undefined; | ||
if (xsrfValue) { | ||
headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; | ||
} | ||
|
||
// send request | ||
promise = sendReq(config, reqData, reqHeaders); | ||
|
||
var serverRequest = function(config) { | ||
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); | ||
|
||
// transform future response | ||
promise = promise.then(transformResponse, transformResponse); | ||
// strip content-type if data is undefined | ||
if (isUndefined(config.data)) { | ||
delete headers['Content-Type']; | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
AGmakonts
|
||
} | ||
|
||
if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { | ||
config.withCredentials = defaults.withCredentials; | ||
} | ||
|
||
// send request | ||
return sendReq(config, reqData, headers).then(transformResponse, transformResponse); | ||
}; | ||
|
||
var chain = [serverRequest, undefined]; | ||
var promise = $q.when(config); | ||
|
||
// apply interceptors | ||
forEach(responseInterceptors, function(interceptor) { | ||
promise = interceptor(promise); | ||
forEach(reversedInterceptors, function(interceptor) { | ||
if (interceptor.request || interceptor.requestError) { | ||
chain.unshift(interceptor.request, interceptor.requestError); | ||
} | ||
if (interceptor.response || interceptor.responseError) { | ||
chain.push(interceptor.response, interceptor.responseError); | ||
} | ||
}); | ||
|
||
while(chain.length) { | ||
var thenFn = chain.shift(); | ||
var rejectFn = chain.shift(); | ||
|
||
promise = promise.then(thenFn, rejectFn); | ||
}; | ||
|
||
promise.success = function(fn) { | ||
promise.then(function(response) { | ||
fn(response.data, response.status, response.headers, config); | ||
|
@@ -584,7 +720,7 @@ function $HttpProvider() { | |
function transformResponse(response) { | ||
// make a copy since the response must be cacheable | ||
var resp = extend({}, response, { | ||
data: transformData(response.data, response.headers, respTransformFn) | ||
data: transformData(response.data, response.headers, config.transformResponse) | ||
}); | ||
return (isSuccess(response.status)) | ||
? resp | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
I know that this is old commit but we have stumbled upon an issue with content type in GET requests and I'm curious what's the reason for removing content type.