AngularJS:使用前/后解析/拒绝操作来增强或包装承诺

时间:2014-08-21 14:46:25

标签: javascript angularjs promise restangular angular-promise

目标

我正在尝试创建一系列承诺'增强器',它将围绕现有的简单http请求的承诺添加功能(如缓存,排队,重定向处理等)。

问题

我正在使用这种增强承诺的方法遇到的问题是,如果增强功能将任何功能或公共可访问的属性添加到承诺中(或者如果我将已经增强的承诺包含在一个非常有效的请求中),那么这些是当我通过返回新的$q将其包装在新的承诺中时丢失了。

问题

我可以使用什么模式来增强或包装承诺(如下面的两个示例中所示),但不会丢失承诺可能具有的任何其他(非冲突)增强功能?

示例1

以下示例将自动处理503-Retry-After错误:

function _enhancePromiseWithAutoRetry(promise) {
  var enhancedPromise = $q(function(resolve, reject) {
    var newReject = get503Handler(this, resolve, reject);
    promise.then(resolve, newReject);
  });

  // 503 handling isn't enabled until the user calls this function.
  enhancedPromise.withAutoRetry = function(onRetry, timeout) {
    var newPromise = angular.copy(this);
    newPromise._503handled = true;
    newPromise._503onRetry = onRetry;
    newPromise._503timeout = timeout;
    return newPromise;
  };

  return enhancedPromise;
}

我的想法是,如果我返回使用上述功能增强的承诺,用户可以去:

someRequest.withAutoRetry().then(onSuccess, onError);

或者更清楚(带链接):

someRequest.then(onSuccess, onAnyError)
           .withAutoRetry().then(onSuccess, onNon503Error);

此处,如果服务器正忙,第一次调用then(...)可能会立即出错,但.withAutoRetry()之后的调用会在重复请求之前轮询服务器,直到响应成功,或者非响应返回RetryAfter错误。

示例2

这是另一个添加自定义缓存行为的示例:

function _enhancePromiseWithCache(promise, cacheGet, cachePut) {
  // Wrap the old promise with a new one that will get called first.
  return $q(function(resolve, reject) {
    // Check if the value is cached using the provided function
    var cachedResponse = cacheGet !== undefined ? cacheGet() : undefined;
    if(cachedResponse !== undefined){
      resolve(cachedResponse);
    } else {
      // Evaluate the wrapped promise, cache the result, then return it.
      promise.then(cachePut);
      promise.then(resolve, reject);
    }
  });
}

这个允许库设置数据缓存,而不是向服务器发出请求,可以在请求完成后添加。例如:

lib.getNameOrigin = function(args) {
  var restRequest = Restangular.all('people').one(args.id).get('nameOrigin');
  // Cache, since all people with the same name will have the same name origin
  var enhancedPromise = _enhancePromiseWithCache(restRequest,
                          function(){ return nameOrigins[args.name]; },
                          function(val){ nameOrigins[args.name] = val; });
  return enhancedPromise;
}

其他地方

// Will transparently populate the cache
lib.getNameOrigin({id: 123, name:'john'}).then(onSuccess, onError).then(...);

其他地方完全

// Will transparently retrieve the result from the cache rather than make request
lib.getNameOrigin({id: 928, name:'john'}).then(onSuccess, onError);

可能的解决方案

我考虑过复制原始的promise,但是后来用一个引用原始promise then的实现(使用Proxy Pattern)覆盖新的then函数,但这是安全?我知道承诺不仅仅是then函数。

2 个答案:

答案 0 :(得分:3)

解决方案不是为了增强承诺本身,而是为了创造它们的工厂。

使用函数式编程和/或面向方面的编程方法来修饰原始函数。这不仅会减少错误,而且更简洁,可组合和可重复使用。

function decorate(makeThenable) {
    return function(...args) {
        … // before creating the thenable
        return makeThenable(...args).then(function(value) {
            … // handle fulfillment
            return …; // the resulting value
        }, function(error) {
            … // handle rejection
            return …; // (or throw)
        });
    };
}
var decorated = decorate(myThenablemaker);
decorated(…).then(whenFulfilled, whenRejected);

示例1:

function withAutoRetry(request, timeout) {
    return function() {
        var args = arguments;
        return request.apply(null, args).catch(function handle(e) {
            if (e instanceof Http503Error) // or whatever
                return request.apply(null, args).catch(handle);
            else
                throw e;
        });
    };
}

withAutoRetry(someRequest)().then(onSuccess, onError);

withAutoRetry(function() {
    return someRequest().then(onSuccess, onAnyError);
})().then(onSuccess, onNon503Error);

示例2:

function withCache(request, hash) {
    var cache = {};
    if (!hash) hash = String;
    return function() {
        var key = hash.apply(this, arguments);
        if (key in cache)
            return cache[key];
        else
            return cache[key] = request.apply(this, arguments);
    };
}

lib.getNameOrigin = withCache(function(args) {
    return Restangular.all('people').one(args.id).get('nameOrigin');
}, function(args) {
    return args.name;
});

答案 1 :(得分:0)

以下是我在可能的解决方案部分中提出的解决方案,以便对其进行详细讨论。

我考虑过复制原始的承诺,但是用一个解决原始承诺的实现来覆盖新版本的then函数,但是这样安全吗?

新示例

function _enhancePromiseWithQueuing(promise, id) {
  // Copy the old promise and overwrite its then method.
  var enhancedPromise = angular.copy(promise);
  enhancedPromise.then = function(resolve, reject) {
    // Resolves the original promise once the existing `id` queue is clear.
    queue.enqueueRequest(id, function() { promise.then(resolve, reject); });
    return this;
  };
  return enhancedPromise;
}

示例1 (来自上方)

function _enhancePromiseWithAutoRetry(promise) {    
  // Copy the old promise and enhance it with the withAutoRetry method.
  var enhancedPromise = angular.copy(promise);
  // Add a function that enables 503 Retry-After handling when called.
  enhancedPromise.withAutoRetry = function(onRetry, timeout) {
    // Copy the old promise and overwrite its then method.
    var promiseWith503Handling = angular.copy(this);
    promiseWith503Handling.then = function(resolve, reject) {
      // Call the original promise then method with a modified reject handler.
      return this.then(resolve, get503Handler(this, resolve, reject,
                                              onRetry, timeout, new Date()));
    };
    return promiseWith503Handling;
  };    
  return enhancedPromise;
}

示例2 (来自上方)

function _enhancePromiseWithCache(promise, cacheGet, cachePut) {
  var enhancedPromise = angular.copy(promise);
  enhancedPromise.then = function(resolve, reject) {
    // Check if the value is cached using the provided function
    var cachedResponse = cacheGet !== undefined ? cacheGet() : undefined;
    if(cachedResponse !== undefined){
      return resolve(cachedResponse);
    } else {
      // Resolve the original promise, cache the result, then return it.
      promise.then(cachePut);
      return promise.then(resolve, reject);
    }
  };
  return enhancedPromise;
}