【问题标题】:Handling errors in multiple calls of AngularJS promises处理多次调用 AngularJS 承诺中的错误
【发布时间】:2014-06-15 12:10:00
【问题描述】:

我有一个带有这种异步 API 的 AngularJS 服务:

myService.asyncCall(['id0', 'id3', 'id2']).then(function (complexData) {   
  // handle complexData
}).catch(function (error) {   
  console.log(error); 
});

asyncCall 封装了多个由$q.all 处理的$http 调用。每个$http 请求都可以响应错误,我希望错误由一个catch 处理程序处理。那么如何实现catch处理程序的多次调用呢?

【问题讨论】:

  • $q.all 在任何承诺被拒绝时被拒绝。你到底想达到什么目的?
  • @ExpertSystem,是的,但是我想处理我传递给$q.all的承诺数组中的每个可能的拒绝
  • 对不起,我还是不明白你想要达到什么目的(这就是我要求澄清的原因)。 “处理 promises 数组中每个可能的拒绝”是什么意思.
  • 好吧,忘了$q.all。我希望我的asyncCall 1. 获得一组承诺,2. 为数组中的每个已解决承诺有一个解析处理程序(示例中的then 块)3. 有一个拒绝处理程序,将为每个被拒绝的承诺调用. (示例中的catch 块)。我希望现在一切都清楚了。
  • 我还有问题 :) 1. 您希望请求并行、顺序或其他方式发生吗? 2. 当任何请求失败时,您希望发生什么?继续其余的或中止所有? 3. 我了解您希望有两个功能:1 用于处理任何请求的成功和一个用于处理任何请求的错误(所以如果 3 个请求成功,您的成功处理程序应该被调用 3 次)。这是正确的还是我仍然没有抓住重点?

标签: javascript angularjs promise q


【解决方案1】:

一种方法是在您的服务中单独附加一个 .catch 处理程序:

function asyncCall(urls){
    var calls = urls.map(makeSomeCall).
                map(function(prom){ return prom.catch(e){ /* recover here */});
    return $q.all(calls);
};

另一种方法是实现settle 方法,该方法类似于$q.all,但会跟踪所有结果。盗用我的回答here

function settle(promises){
     var d = $q.defer();
     var counter = 0;
     var results = Array(promises.length);
     promises.forEach(function(p,i){ 
         p.then(function(v){ // add as fulfilled
              results[i] = {state:"fulfilled", promise : p, value: v};
         }).catch(function(r){ // add as rejected
              results[i] = {state:"rejected", promise : p, reason: r};
         }).finally(function(){  // when any promises resolved or failed
             counter++; // notify the counter
             if (counter === promises.length) {
                d.resolve(results); // resolve the deferred.
             }
         });
     });
})

您可以像这样处理多个承诺:

var promiseArr = ["id0","id3","id2"].map(makePromise);

settle(promiseArr).then(function(results){
    var failed = results.filter(function(r){ return r.state === "rejected"; });
    var failedValues = failed.map(function(i){ return i.value; });
    var done = results.filter(function(r){ return r.state === "fulfilled"; });
     var doneValues = done.map(function(i){ return i.value; }); 
});

这使您可以访问所有承诺的结果,无论它是否失败,并让您更细致地恢复。

您的服务应该处理该聚合,因为它是对所有内容返回承诺的服务。一个例子是:

if(failed.length > 0){
     throw new Error("The following failed..."); // some info about all failed
}

【讨论】:

  • 如果我想在每次$http 回答错误时显示错误通知怎么办?这就是为什么我想要多次调用catch
  • 看看我建议的第一种方法。应该这样做。
  • 考虑过这样的解决方案,但在这种情况下,如果至少有一个承诺将被拒绝(因为 all 行为),则不会调用 then 处理程序。换句话说,需要一种all,它将为所有已解决的承诺调用then,为每个被拒绝的承诺调用catch
  • 在同步代码中看起来如何? @e.gluhotorenko
【解决方案2】:

如果我理解正确(最后),这就是你想要实现的目标:

  • 输入:
    1. 承诺列表
    2. 当所有承诺都已解决时调用的回调
    3. 每次任何承诺被拒绝时都会调用一个回调

  • 行为:
    1. 您希望并行执行(不是顺序执行)。
    2. 您希望所有的 Promise 要么被解决要么被拒绝(即,即使一个 Promise 被拒绝,其余的也应该照常继续)。
    3. 您希望只调用一次“最终”回调(接收结果数组 - 无论相应的延迟是解决还是拒绝)。

$q.all 应该“增强”以满足您的需求,因为默认情况下:

  1. 一旦列表中的任何承诺被拒绝,它就会立即被拒绝。
  2. 如果被拒绝,它只返回拒绝原因,返回结果列表。

这是一个可能的实现:

function asyncCall(listOfPromises, onErrorCallback, finalCallback) {

  listOfPromises  = listOfPromises  || [];
  onErrorCallback = onErrorCallback || angular.noop;
  finalCallback   = finalCallback   || angular.noop;

  // Create a new list of promises that can "recover" from rejection
  var newListOfPromises = listOfPromises.map(function (promise) {
    return promise.catch(function (reason) {

      // First call the `onErrroCallback`
      onErrorCallback(reason);

      // Change the returned value to indicate that it was rejected
      // Based on the type of `reason` you might need to change this
      // (e.g. if `reason` is an object, add a `rejected` property)
      return 'rejected:' + reason;

    });
  });

  // Finally, we create a "collective" promise that calls `finalCallback` when resolved.
  // Thanks to our modifications, it will never get rejected !
  $q.all(newListOfPromises).then(finalCallback);
}

另请参阅此short demo

【讨论】:

  • 是的,几乎是我要找的东西,但唯一的例外是我只需要在成功处理程序中解析结果而不需要被拒绝的结果。任何想法如何更改您的all 修改?
  • 删除一些结果一般不是很有用,因为那样就无法知道哪个结果属于哪个promise。在任何情况下,您都可以在将结果传递给finalCallback 之前对其进行过滤。例如。将.then(finalCallback) 替换为.then(function(results){finalCallback(results.filter(function(res){return res.indexOf('rejected:') !== 0;}));})
  • 如果只有在没有被拒绝的 promises 的情况下才需要执行 finalCallback 怎么办???
  • @ppollono:在调用 onErrorCallback() 之前添加一个设置为 true 的标志,并在调用 finalCallback 之前检查它。这真的取决于您的具体要求。
猜你喜欢
  • 1970-01-01
  • 2014-11-18
  • 2015-05-24
  • 2018-06-01
  • 2016-03-02
  • 1970-01-01
  • 2015-07-29
  • 1970-01-01
  • 2013-09-28
相关资源
最近更新 更多