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 | 
