在 AngularJS 中使用带有 Promises 的 success/error/finally/catch

Posted

技术标签:

【中文标题】在 AngularJS 中使用带有 Promises 的 success/error/finally/catch【英文标题】:Using success/error/finally/catch with Promises in AngularJS 【发布时间】:2014-06-26 21:10:47 【问题描述】:

我在 AngularJs 中使用$http,但我不确定如何使用返回的承诺和处理错误。

我有这个代码:

$http
    .get(url)
    .success(function(data) 
        // Handle data
    )
    .error(function(data, status) 
        // Handle HTTP error
    )
    .finally(function() 
        // Execute logic independent of success/error
    )
    .catch(function(error) 
        // Catch and handle exceptions from success/error/finally functions
    );

这是一个好方法,还是有更简单的方法?

【问题讨论】:

【参考方案1】:

您在寻找哪种类型的粒度?您通常可以通过:

$http.get(url).then(
  //success function
  function(results) 
    //do something w/results.data
  ,
  //error function
  function(err) 
    //handle error
  
);

我发现在链接多个 Promise 时,“finally”和“catch”会更好。

【讨论】:

在您的示例中,错误处理程序仅处理 $http 错误。 是的,我仍然需要处理成功/错误函数中的异常。然后我需要某种通用处理程序(我可以在其中设置loading = false之类的东西) 你有一个大括号而不是括号来关闭你的 then() 调用。 这不适用于 404 响应错误,仅适用于 .catch() 方法 这是处理返回给控制器的http错误的正确答案【参考方案2】:

Promises 是对语句的抽象,它允许我们使用异步代码同步表达自己。它们代表一次性任务的执行。

它们还提供异常处理,就像普通代码一样,您可以从 Promise 中返回,也可以抛出。

你想要的同步代码是:

try
  try
      var res = $http.getSync("url");
      res = someProcessingOf(res);
   catch (e) 
      console.log("Got an error!",e);
      throw e; // rethrow to not marked as handled
  
  // do more stuff with res
 catch (e)
     // handle errors in processing or in error.

promisified 版本非常相似:

$http.get("url").
then(someProcessingOf).
catch(function(e)
   console.log("got an error in initial processing",e);
   throw e; // rethrow to not marked as handled, 
            // in $q it's better to `return $q.reject(e)` here
).then(function(res)
    // do more stuff
).catch(function(e)
    // handle errors in processing or in error.
);

【讨论】:

您将如何使用success()error()finally()catch() 结合使用?还是我必须使用then(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp); @Joel 通常,您不想使用successerror(更喜欢.then.catch,您可以(并且应该)省略errorFunction.then 使用 accatch 就像我上面的代码一样。 @BenjaminGruenbaum 您能否详细说明为什么您建议避免使用success/error?我的 Eclipse 在看到.catch( 时也会运行异常,所以我现在使用["catch"](。如何驯服 Eclipse? Angular 的 $q 库的 $http 模块实现使用 .success 和 .error 而不是 .then 和 .catch。然而,在我的测试中,当使用 .then 和 .catch 承诺时,我可以访问 $http 承诺的所有属性。另请参阅 zd333 的回答。 @SirBenBenji $q 没有 .success.error,$http 返回一个 $q 承诺加上 successerror处理程序 - 但是,这些处理程序不会链接,如果/可能的话,通常应避免使用。一般来说 - 如果您有问题,最好将它们作为一个新问题提出,而不是作为对旧问题的评论。【参考方案3】:

在 Angular $http 的情况下,success() 和 error() 函数将解包响应对象,因此回调签名将类似于 $http(...).success(function(data, status, headers,配置))

对于 then(),您可能会处理原始响应对象。 比如贴在AngularJS $http API 文档中

$http(
        url: $scope.url,
        method: $scope.method,
        cache: $templateCache
    )
    .success(function(data, status) 
        $scope.status = status;
        $scope.data = data;
    )
    .error(function(data, status) 
        $scope.data = data || 'Request failed';
        $scope.status = status;
    );

最后的 .catch(...) 不需要,除非在之前的 Promise 链中抛出了新的错误。

【讨论】:

不推荐使用成功/错误方法。【参考方案4】:

我认为前面的答案是正确的,但这是另一个例子(只是一个 f.y.i,success() 和 error() 根据 AngularJS Main page 已弃用:

$http
    .get('http://someendpoint/maybe/returns/JSON')
    .then(function(response) 
        return response.data;
    ).catch(function(e) 
        console.log('Error: ', e);
        throw e;
    ).finally(function() 
        console.log('This finally block');
    );

【讨论】:

据我所知,最终没有返回响应。【参考方案5】:

忘记使用successerror 方法吧。

这两种方法在 Angular 1.4 中都已弃用。基本上,弃用背后的原因是它们不是 chainable-friendly,可以这么说。

通过以下示例,我将尝试说明successerror链式友好 的含义。假设我们调用一个 API,返回一个带有地址的用户对象:

用户对象:

name: 'Igor', address: 'San Francisco'

调用 API:

$http.get('/user')
    .success(function (user) 
        return user.address;   <---  
    )                            |  // you might expect that 'obj' is equal to the
    .then(function (obj)    ------  // address of the user, but it is NOT

        console.log(obj); // -> name: 'Igor', address: 'San Francisco'
    );
;

发生了什么?

因为successerror返回的是原来的promise,即$http.get返回的那个,所以对象传递给then的回调是整个 user 对象,也就是说与前面的success 回调相同的输入。

如果我们将两个then 链接起来,就不会那么混乱了:

$http.get('/user')
    .then(function (user) 
        return user.address;  
    )
    .then(function (obj)   
        console.log(obj); // -> 'San Francisco'
    );
;

【讨论】:

另外值得注意的是successerror$http调用的only added to the immediate return(不是原型),所以如果你在它们之间调用另一个promise方法(比如,你通常调用return $http.get(url) 包装在基础库中,但后来决定使用 return $http.get(url).finally(...) 切换库调用中的微调器),那么您将不再拥有那些方便的方法。【参考方案6】:

我这样做就像 Bradley Braithwaite 在他的 blog 中建议的那样:

app
    .factory('searchService', ['$q', '$http', function($q, $http) 
        var service = ;

        service.search = function search(query) 
            // We make use of Angular's $q library to create the deferred instance
            var deferred = $q.defer();

            $http
                .get('http://localhost/v1?=q' + query)
                .success(function(data) 
                    // The promise is resolved once the HTTP call is successful.
                    deferred.resolve(data);
                )
                .error(function(reason) 
                    // The promise is rejected if there is an error with the HTTP call.
                    deferred.reject(reason);
                );

            // The promise is returned to the caller
            return deferred.promise;
        ;

        return service;
    ])
    .controller('SearchController', ['$scope', 'searchService', function($scope, searchService) 
        // The search service returns a promise API
        searchService
            .search($scope.query)
            .then(function(data) 
                // This is set when the promise is resolved.
                $scope.results = data;
            )
            .catch(function(reason) 
                // This is set in the event of an error.
                $scope.error = 'There has been an error: ' + reason;
            );
    ])

关键点:

resolve 函数链接到控制器中的 .then 函数,即一切正常,因此我们可以信守承诺并解决它。

reject 函数链接到我们控制器中的 .catch 函数,即出现问题,因此我们无法兑现承诺,需要 拒绝它。

它非常稳定和安全,如果您有其他条件拒绝promise,您可以随时在成功函数中过滤您的数据并致电deferred.reject(anotherReason)并说明拒绝的原因。

正如 Ryan Vice 在 cmets 中建议的那样,这可能不会被视为有用,除非您对响应稍作调整,可以这么说。

因为 successerror 自 1.4 起已弃用,也许最好使用常规的 promise 方法 thencatch 并在这些方法中转换响应并返回转换后响应的 promise。

我展示了两种方法和第三种中间方法的相同示例:

successerror 方法(successerror 返回一个 HTTP 响应的承诺,所以我们需要 $q 的帮助来返回一个数据的承诺):

function search(query) 
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)
  .success(function(data,status) 
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(data);              
  )

  .error(function(reason,status) 
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.error)
      deferred.reject(text:reason.error, status:status);
    else
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject(text:'whatever', status:500);
    
  );

  // The promise is returned to the caller
  return deferred.promise;
;

thencatch 方法(这有点难以测试,因为抛出):

function search(query) 

  var promise=$http.get('http://localhost/v1?=q' + query)

  .then(function (response) 
    // The promise is resolved once the HTTP call is successful.
    return response.data;
  ,function(reason) 
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText)
      throw reason;
    else
      //if we don't get any answers the proxy/api will probably be down
      throw statusText:'Call error', status:500;
    

  );

  return promise;

虽然有一个中途的解决方案(这样您可以避免 throw 并且无论如何您可能需要使用 $q 来模拟测试中的承诺行为):

function search(query) 
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)

  .then(function (response) 
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(response.data);
  ,function(reason) 
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText)
      deferred.reject(reason);
    else
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject(statusText:'Call error', status:500);
    

  );

  // The promise is returned to the caller
  return deferred.promise;

欢迎任何形式的 cmets 或更正。

【讨论】:

为什么要使用 $q 将 promise 包装在 promise 中。为什么不直接返回 $http.get() 返回的 promise? 因为success()error() 不会像then() 那样返回新的承诺。使用$q,我们让工厂返回数据的承诺,而不是 HTTP 响应的承诺。 你的回答让我很困惑,所以也许我没有很好地解释自己。除非您正在操纵响应,否则您可以简单地返回 $http 返回的承诺。看看我刚刚写的这个例子:jsbin.com/belagan/edit?html,js,output 我看不到价值。我觉得没有必要,我拒绝对使用这种方法的项目进行代码审查,但如果你从中获得价值,那么你应该使用它。我还在 Angular 最佳实践文章中看到了一些承诺,称不必要的包装是一种气味。 这是deferred anti-pattern。阅读You're Missing the Point of Promises

以上是关于在 AngularJS 中使用带有 Promises 的 success/error/finally/catch的主要内容,如果未能解决你的问题,请参考以下文章

如何对 AngularJS $promise.then 进行单元测试

Promise.catch() 不会在 AngularJS 单元测试中捕获异常

使用 angularJS 和 Typescript 的 Promise

如何在angularjs中链接promise错误函数

angularJS中XHR与promise

angularJS笔记之Promise