AngularJS中的Promise,协助理解resolve

时间:2022-03-30 09:51:32

一.Promise

        Promise是一个接口,它用来处理的对象具有这样的特点:在未来某一时刻(主要是异步调用)会从服务端返回或者被填充属性。其核心是,promise是一个带有then()函数的对象。

        为了展示它的优点,下面来看一个例子,其中需要获取用户当前的配置文件:

Js代码   AngularJS中的Promise,协助理解resolve
  1. var currentProfile = null;  
  2. var username = 'something';  
  3.   
  4. fetchServerConfig(function(serverConfig) {  
  5.     fetchUserProfiles(serverConfig.USER_PROFILES, username,  
  6.          function(profiles) {  
  7.              currentProfile = profiles.currentProfile;  
  8.      });  
  9. });  

        上面这种处理方式存在一些问题:

1.对于代码缩进来说,这种代码就是一个噩梦,尤其在你需要链式调用很多次的时候;

2.处于回调和函数之间的错误报告非常容易丢失,除非你在每一个步骤中都手动处理错误;

3.如果需要使用currentProfile对象来做一些事情,那么你需要在最内层的回调中封装真正想要实现的逻辑,要么直接封装,要么通过一个单独的函数来封装。

        Promise机制可以很好地解决这些问题。在深入了解其运行机制之前,我们来看看如何使用promise实现同样的事情:

Js代码   AngularJS中的Promise,协助理解resolve
  1. var currentProfile =   
  2.     fetchServerConfig().then(function(serverConfig) {  
  3.        return fetchUserProfiles(serverConfig.USER_PROFILES, username);  
  4.     }).then(function(profiles) {  
  5.        return profiles.currentProfile;  
  6.     },function(error) {  
  7.        //可以在这里处理错误,在fetchServerConfig或者fetchUserProfiles中处理都可以  
  8.     });  

        使用promise机制的优点如下:

1.可以对函数进行链式调用,所以你不会陷入代码缩进噩梦中;

2.在调用链的过程中,可以保证上一个函数调用完成之后才会调用下一个函数;

3.每一个then()调用都带有两个参数(两个都是函数)。第一个是成功之后的回调,第二个是出错之后的处理器;

4.如果调用链中出现了错误,错误将会被冒泡传递到其余的错误处理函数中。所以,最终来说,所有错误都可以在任意一个回调函数中进行处理。

        你可能会问,resolve(解决)方法和reject(拒绝)方法又是什么呢?在AngularJS中,延迟调用是实现promise的一种方式。调用resolve方法将会填充promise(也就是调用success处理函数),而调用reject方法将会调用promise的错误处理函数。

 

二.$q和Promise

        Promise接口是AngularJS组织API的基础,从根本上讲,Promise接口从以下方面对异步请求做了规范:

        a.异步请求返回一个promise,而不是返回具体值;

        b.Promise带有一个then函数,这个函数有两个参数:第一个参数是处理"resolved"和"sucess"事件的函数;第二个参数是处理"rejected"和"failure"事件的函数。调用这两个函数时将会把结果或者拒绝的原因作为参数传递进去;

        c.只要返回结果是合法的,接口就可以保证这两个函数中的一个会被调用。

        大多数deferred/Q实现都会遵守以上方式,但是AngularJS的实现比较特殊,原因如下:

        a.AngularJS知道$q的存在,所以$q会被整合到作用域模型中去。这样可以使解析时的传递速度更快,并且可以减少UI的闪烁和刷新;

        b.AngularJS的模板也认识$q,这样一来,接口的内容就可以被当作最终结果值(而不是当作promise)来对待,然后等获取结果之后再通知promise;

        c.体积更小,因为对于常用的异步任务来说,AngularJS只实现了它们所需要的最基本、最重要的功能。

        我们来看一段代码:

Js代码   AngularJS中的Promise,协助理解resolve
  1. fetchUser(function(user) {  
  2.     fetchUserPermissions(user, function(permissions) {  
  3.         fetchUserListData(user, permissions, function(list) {  
  4.             //对你想要显示的数据列表做一些处理  
  5.         });  
  6.     });  
  7. });  

        使用JavaScript时,人们经常会抱怨这种可怕的、金字塔的代码缩进噩梦。从本质上来说,异步返回的方式和程序的同步处理之间存在冲突,从而导致了多重嵌套的函数,这样就更难跟踪到当前的上下文了。

        另外,对错误的处理也存在同样的问题。处理错误的最佳方式是什么?你会在每一个步骤中都处理错误吗?那样会把代码搞得一团糟。

        为了解决这一问题,Promise方案提供了then的概念,在成功的情况下会执行一个函数,在出错的情况下执行另一个函数,两个函数都可以进行链式调用。所以,对于上面这段代码,如果使用Promise API(至少使用AngularJS的实现),可以这样展开:

Js代码   AngularJS中的Promise,协助理解resolve
  1. var deferred = $q.defer();  
  2.   
  3. var fetchUser = function() {  
  4.      //在进行异步调用之后,使用响应值调用deferred.resolve  
  5.      deferred.resolve(user);  
  6.        
  7.      //在出错的情况下调用  
  8.      deferred.reject('Reason for failure');  
  9. }  
  10. //类似地,处理fetchUserPermissions和fetchUserListData  
  11.   
  12. deferred.promise.then(fetchUser)  
  13.    .then(fetchUserPermissions)  
  14.    .then(fetchUserListData)  
  15.    .then(function(list) {  
  16.         //处理数据列表  
  17.    },function(errorReason( {  
  18.        //在任何一个步骤中所发生的错误,都可以在这里处理  
  19. });  

        整个金字塔式的代码就被很好的平坦化了,并且提供了链式的作用域,以及一个单一的出错处理点。你可以在应用中使用同样的代码来处理异步调用,只要导入AngularJS的$q服务即可。

 

三.拦截响应

        Promise机制还可以做一些非常酷的事情:拦截响应。

        我们已经学过的内容有:向服务端发送请求、处理响应、把响应很好地包装成抽象的东西及处理异步调用。但是在真实的应用中,对于每一次服务端调用,最终还必须做一些通用的操作,例如错误处理、鉴权以及其他安全方面的处理(例如剪裁数据)。

        在深入理解了$q接口之后,我们就可以使用拦截响应的方式来处理以上所有任务了。响应拦截的机制允许我们在响应到达应用之前对其进行拦截,并在上面进行一些操作,例如转换数据形式、处理错误等所有你能想到的操作。

        下面来看一个例子,它会拦截响应,然后做一些很小的数据转换操作。

Js代码   AngularJS中的Promise,协助理解resolve
  1. //把拦截器注册为一个服务  
  2. myModule.factory('myInterceptor'function($q, notifyService, errorLog) {  
  3.      return function(promise) {  
  4.           return promise.then(function(response) {  
  5.                //什么都不做  
  6.                return response;  
  7.           }, function(response) {  
  8.                //notify服务将会使用错误信息来刷新UI  
  9.                notifyService(response);  
  10.                //同时把错误信息打印到控制台,以便调试  
  11.                errorLog(response);  
  12.                return $q.reject(response);  
  13.           });  
  14.      }  
  15. });  
  16.   
  17. //确保我们所创建的拦截器是拦截器链的一部分  
  18. $httpProvider.responseInterceptors.push('myInterceptor');  

 

资料来源:《用AngularJS开发下一代Web应用》