Javascript AngularJS:通过前/后解决/拒绝行动增强或包装承诺

Javascript AngularJS:通过前/后解决/拒绝行动增强或包装承诺,javascript,angularjs,promise,restangular,angular-promise,Javascript,Angularjs,Promise,Restangular,Angular Promise,目标 我正在尝试创建一系列承诺“增强器”,它们将围绕简单http请求的现有承诺添加功能(如缓存、排队、重定向处理等) 问题 我在使用这种增强承诺的方法时遇到的问题是,如果一个增强向承诺添加了任何函数或可公开访问的属性(或者如果我将一个已经增强的承诺包装为重新启动的请求),那么当我通过返回一个新的$q将其包装为一个新的承诺时,这些函数或属性就会丢失 问题 我可以使用什么模式来增强或包装承诺(如下面的两个示例),但不会丢失承诺可能具有的任何其他(非冲突的)增强 示例1 下面是一个将自动处理503错误

目标

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

问题

我在使用这种增强承诺的方法时遇到的问题是,如果一个增强向承诺添加了任何函数或可公开访问的属性(或者如果我将一个已经增强的承诺包装为重新启动的请求),那么当我通过返回一个新的
$q
将其包装为一个新的承诺时,这些函数或属性就会丢失

问题

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

示例1

下面是一个将自动处理503错误后重试的示例:

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);
或者更清楚地说(使用链接):

在这里,如果服务器忙,对
然后(…)
的第一次调用可能会立即出错,但是
之后的调用将使用重复请求轮询服务器,直到响应成功,或者返回非
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);
可能的解决方案


我曾考虑复制原始承诺,但随后使用引用原始承诺的
then
(使用)的实现覆盖新承诺的
then
函数,但这安全吗?我知道承诺远不止是
then
函数。

这是我在可能的解决方案一节中提出的解决方案,可以详细讨论

我曾考虑复制原始承诺,但随后使用解决原始承诺的实现覆盖新承诺的
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(自上而下)

示例2(自上而下)


解决办法不是强化承诺本身,而是强化创造承诺的工厂

使用函数式编程和/或面向方面的编程方法来修饰原始函数。这不仅不容易出错,而且更加简洁、可组合和可重用

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;
        });
    };
}
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);
    };
}

示例2:

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;
        });
    };
}
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);
    };
}


这在角度1.2和角度1.3之间变化,这与您相关吗?你希望我关注哪一个?在这两种情况下,您都必须覆盖
然后
,只要您的
,它就是安全的。然后
是Promises/A+complaintAlso,我不确定我是否理解您的用例
$http(…).cache()。然后(…
有意义,但是
$http(…)。然后(function(){return 3})。cache()
没有意义,3被缓存了吗?在哪里?到了什么程度?这些方法只会影响另一面,这实际上是有意义的。你能更具体地说明你在添加什么吗?@BenjaminGruenbaum angular的最新版本很好。
.catch()呢
方法?是否也必须重写该方法,还是保证只调用
。然后(未定义,拒绝)
?保证?不,但实际上确实如此。它只保证具有相同的效果。尽管-我认为我所写的关于链接问题的文章从一开始就很重要。链接后缓存或排队意味着什么?@BenjaminGruenbaum第二个用例是库
返回enhancePromiseWithCache($http(…),cache.makeGetterFor(args),cache.makePutterFor(args))
。用户通常会返回consume承诺:
someRequest.then(onSuccess,onFailure)。then(somethingElse);
,只有
$http
请求/响应被拦截。覆盖
then
可能是安全的,但需要大量注意(容易出错),可能会破坏promise library提供的大多数优化;以及其他一些依赖于库的功能。@Bergi我认为这会很危险,但我找不到任何可能导致问题的示例。如果没有错误,它可能会破坏哪些功能或优化?我可以在您的I实现是新的
then
不返回任何内容。诸如进度消息或取消(无论promise库支持什么)等功能需要集成到解决方案中,并使其非常依赖于实现(与我的方法不同,在交换promise库时不需要修改)。优化也依赖于实现,许多库使用比相关承诺的
then
方法更小的原语来实现它们的
then
方法。没错,在从
$q
构造函数切换后,我错过了添加返回语句。我发现很难将您的原语与特定功能的校正(缓存/503处理)从您的一般方法来看。据我所知,您的方法主要与使用
apply
有关。您是否可以详细说明或添加一个示例,其中包括使用发生的任意操作包装任意启用:1.在
resolve
之前,2.在
resolve
之后,3.在
之前拒绝
>,4.在
reject
之后?从示例2中,它看起来像是调用
lib.getNameOrigin({id:123,name:'john'}
withAutoRetry(someRequest)().then(onSuccess, onError);

withAutoRetry(function() {
    return someRequest().then(onSuccess, onAnyError);
})().then(onSuccess, onNon503Error);
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;
});