diff options
| author | Sylvester Keil | 2013-02-26 10:22:12 +0100 |
|---|---|---|
| committer | James deBoer | 2013-03-27 13:13:59 -0700 |
| commit | 4ae46814ff4e7c0bbcdbbefc0a97277283a84065 (patch) | |
| tree | 2c5bcbe2d5bcc143043cb989d8b40899eb5ef8f9 /src/ng/http.js | |
| parent | 5c735eb4ab07144a62949472ed388cb185099201 (diff) | |
| download | angular.js-4ae46814ff4e7c0bbcdbbefc0a97277283a84065.tar.bz2 | |
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');
});
Diffstat (limited to 'src/ng/http.js')
| -rw-r--r-- | src/ng/http.js | 214 |
1 files changed, 175 insertions, 39 deletions
diff --git a/src/ng/http.js b/src/ng/http.js index e4d695c4..d54e8bd3 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -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']; + } + + 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 |
