在AngularJS中使用Promises时,如何使用success/error/finally/catch?

119

我正在使用AngularJs中的$http,但我不确定如何使用返回的Promise并处理错误。

这是我的代码:

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

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

6个回答

106

Promise是一个对语句的抽象,使我们能够同步地与异步代码表达自己。它们代表了一次性任务的执行。

它们也提供异常处理,就像普通代码一样,您可以从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.
}

改造后的版本非常相似:

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

4
要结合 catch() 使用 success()error()finally(),您可以按照以下方式处理:somePromise() .then(successFunction) .catch(errorFunction) .finally(cleanUp)这将在 Promise 成功后执行 successFunction,如果 Promise 失败,则执行 errorFunction。然后,无论 Promise 成功或失败,都会执行 cleanUp。您还可以使用 try-catch 块和 async/await 来捕获和处理错误。如果您希望使用 then(),那么您可以使用以下代码:somePromise() .then(successFunction, errorFunction) .then(cleanUp) .catch(exceptionHandling)这将在 Promise 成功时调用 successFunction,在 Promise 失败时调用 errorFunction,在成功或失败后都会执行 cleanUp。 如果发生异常,则会在 catch() 中捕获并进行处理。 - Joel
3
通常情况下,您不希望使用 successerror(而是更喜欢使用 .then.catch),在使用 .then 时可以省略 errorFunction,并像我的代码中一样使用 catch - Benjamin Gruenbaum
@BenjaminGruenbaum,您能详细说明为什么建议避免使用 success/error 吗?另外,当 Eclipse 看到 .catch( 时,我的 Eclipse 就会失控,所以我现在使用 ["catch"](。我该如何驯服 Eclipse? - Giszmo
Angular的$http模块实现了$q库,使用.success和.error代替.then和.catch。然而,在我的测试中,当使用.then和.catch承诺时,我可以访问$http承诺的所有属性。另请参见zd333的答案。 - Stephan Kristyn
3
@SirBenBenji $q 没有 .success.error,$http 返回一个 $q promise 并附加 successerror 处理程序,然而这些处理程序不会链式调用,如果可能的话应该避免使用。通常来说,如果您有疑问,最好将它们作为一个新问题提出,而不是在旧问题的评论中提问。 - Benjamin Gruenbaum
显示剩余3条评论

44

不要再使用 successerror 方法。

这两种方法在 Angular 1.4 中已被弃用。基本上,它们被弃用的原因是它们不太适合进行链式调用,可以这么说。

通过下面的示例,我会尝试演示一下关于 successerror 不适合进行链式调用的意思。假设我们调用一个 API ,该 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返回的那个promise对象,所以传递给then回调函数的参数是整个user对象,也就是前面的success回调函数中的输入。

如果我们将两个then链接起来,这会少些些混淆:

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

1
值得注意的是 successerror 仅在 $http 调用的即时返回中添加(而不是原型),因此如果它们之间调用了另一个 Promise 方法(例如,通常会使用包装库调用 return $http.get(url),但稍后决定在库调用中使用 return $http.get(url).finally(...) 切换旋转器),则您将不再拥有这些便利方法。 - drzaus

40

我认为之前的答案都是正确的,但这里有另一个例子(仅供参考,根据AngularJS的主页,success()和error()已被弃用):

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

3
据我所知,最终不会返回响应。 - diplosaurus

11

您需要什么类型的细节粒度?通常您可以使用以下方法:

$http.get(url).then(
  //success function
  function(results) {
    //do something w/results.data
  },
  //error function
  function(err) {
    //handle error
  }
);
我发现在链式调用多个Promise时,“finally”和“catch”更为实用。

1
在你的例子中,错误处理程序仅处理 $http 错误。 - Benjamin Gruenbaum
1
是的,我仍然需要在成功/错误函数中处理异常。然后我需要一些通用的处理程序(在那里我可以设置诸如loading = false之类的东西)。 - Joel
1
你把大括号放在了 then() 调用的地方,而不是圆括号。 - Paul McClean
1
这个方法不能处理404响应错误,只能在.catch()方法中使用。 - elporfirio
这是处理返回给控制器的HTTP错误的正确答案。 - Leon

5
在Angular的$http情况下,success()和error()函数将解包响应对象,所以回调签名将像$http(...).success(function(data, status, headers, config))一样。
对于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 链中出现新错误时才需要。

2
成功/错误方法已被弃用。 - OverMars

-3

我按照 Bradley Braithwaite 在他的 博客 中建议的方法来做:

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函数,即出现问题,因此我们无法遵守承诺并需要拒绝它。
如果您有其他条件来拒绝承诺,那么在成功函数中过滤数据并使用拒绝原因调用deferred.reject(anotherReason)始终是相当稳定和安全的。
正如Ryan Vice在评论中建议的那样,这可能不会被视为有用,除非您稍微调整一下响应。
由于自1.4版本以来已弃用success和error,因此最好使用常规的promise方法then和catch,并在这些方法中转换响应并返回该转换响应的承诺。
我将展示两种方法和第三种介于两者之间的方法的相同示例。

successerror 方法(successerror 返回一个 HTTP 响应的 Promise,因此我们需要使用 $q 来返回数据的 Promise):

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 方法(由于 throw 的存在,这个测试会稍微有些困难):

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来模拟Promise的行为):

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

欢迎任何形式的评论或更正。


3
为什么要使用$q来包装一个promise?为什么不直接返回$http.get()返回的promise呢? - Ryan Vice
很好的观点@RyanVice,现在已经更正了。除此之外,你还喜欢哪个选项? - Watchmaker
你可以在上面的评论中查看我的fiddle以获取我推荐的模式,但基本上它是这样的: return $http.get('http://localhost/v1?=q' + query) }``` - Ryan Vice
2
我不认为这有价值。对我来说感觉是不必要的,我拒绝使用这种方法对我的项目进行代码审查,但如果你从中获得了价值,那么你应该使用它。我也看到一些Angular最佳实践文章中承诺指出不必要的包装是一种坏味道。 - Ryan Vice
2
这是一个延迟反模式。请阅读你误解了Promise的用途 - georgeawg
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接