听风是风

学或不学,知识都在那里,只增不减。

导航

angular $q promise详解

前言

通过本文,你大概能清楚angular promise是个啥,$q又是个啥,以及怎么用它。这里咱们先灌输下promise的思想。

下面写的全是废话,一些看着高逼格其实没什么大作用的概念,想知道$q究竟是什么,怎么去用,建议跳到文章尾部的补充部分,在知道使用后再去补这些较为详细的概念。

一、从promise起步

promise啥意思?打开有道词典=>输入promise=>点击搜索,如图:

   

OK,一个承诺,许诺,何为许诺?

打完这场仗,我就回老家娶你,flag高高挂起。这就是一个许诺。我告诉你我会娶你,但至于上了战场我可能挂了(响应失败),也许能顺利回来(响应成功),这不是我能确定的,只能先承诺你,你可以在家等我,也可以选择嫁给隔壁老王,我不会在行为上限制你。

其实对于promise的理解,这两天参考了下其它文章,觉得还是父子看天气的一篇举例文章最为精妙,我就直接引用精简分析下,不把它的插画啥的搬过来了,这是知乎翻译这是英文原文

父亲需要儿子去后山山坡上通过望远镜观察天气,再判断是否出海捕鱼,儿子出发前许诺(promise)父亲半小时后回来(在这个时间线上这是异步的),而父亲可以在这段期间做自己的事情。那么出现以下几种情况:

情况一:山坡上一眼望去,远方阳光明媚,天气信息获取成功,儿子说OjbK,于是父亲顺利出海捕鱼。承诺兑现,promise=》resolved

情况二:山坡上一眼望去,远方乌云密布,天气信息获取成功,儿子说问题很大,于是父亲决定在家休息。承诺兑现,promise=》resolved

情况三:山坡上云雾缭绕,宛如仙境,一眼望去啥都看不到,天气信息获取失败,父亲觉得有风险,还是在家休息。承兑未兑现,promise=>rejected

那么我们可以将情况一与情况二理解为一次异步的数据请求,都请求到了结果,只是数据得到不同罢了,而情况三则是请求失败,啥数据也没拿到手。

那么我们把上面的故事代码化,这里还是直接借用知乎文代码,稍作注释便于理解。

  我们把儿子上山看天气比喻成一个service服务,而父亲会使用这个service服务去获取天气信息,那么先封装service服务。代码如下:

  angular.module("myApp",[])
      //将获取天气的行为封装为sonService服务
          .factory("sonService",function($http,$q){
              return {
                  getWeather: function(){
                      return $http.get({
                          method:"GET",//定义http请求方法
                          url:""//这是你要请求的地址
                      }).then(function success(resp){
                          if(resp.data==="good"){
                              //如果请求的结果OK,那咱们通知父亲出海吧
                              return resp.data
                          }else{
                              //否则别出海了
                              return $q.reject(resp.data)
                          }
                      },function error(resp){
                          //没请求回来也别出海了,不值得冒险
                          return $q.reject(resp.data)
                      })
                  }
              }
          })
var makePromiseWithSon = function () {
    // 这里咱们开始调用封装的获取天气函数
    SonService.getWeather()
    .then(function (data) {
    // 如果信息获取成功,且是好的
        if (data.forecast === 'good') {
            //准备出去捕鱼
            prepareFishingTrip();
        } else {
            //否则准备午餐
            prepareSundayRoastDinner();
        }
    }, function (error) {
    // 请求失败,还是准备午餐
        prepareSundayRoastDinner();
    });
};

  OK,从灌输promise理念,到模拟了一个小故事,大概说到这里了,在以上代码中,你一定纳闷,$q是啥啊,resolvedrejected又是个啥玩意,没事,咱们下面借着叨叨。

二、从promise谈到$q

  如果把promise理解为一种异步编程思想,我们可以把$q看成angular对于这种思想的封装,就是咱们可以直接使用$q来实现异步编程的目的。

  我们使用promise的核心目的,还是能及时获得功能组合以及错误冒泡的同时,保持代码异步运行的能力。

  重点来了

  1.在promise中,只有一个resolve或者reject会被调用,二者只执行其中一个。

     resolve被调用时,会带有一个履行值,就是需要返回已请求成功的值。

     reject被调用时,要带一个拒绝原因,就是被拒绝后,需要返回请求失败的数据,包含status之类的。

  2.如果promise被执行或者拒绝了,依赖于它们的处理程序仍然会被调用。

  3.处理程序总是会被异步调用。

  那现在开始尝试使用$q吧,首先,我们需要将$q注入到我们想要使用它的对象, 因为angular中已经包含了这个服务,所以就不用额外引入别的js文件了。

angular.module('myApp', [])
.factory('GithubService', function($q) {
  // 现在就可以访问到$q库了
});

  注入完成之后,我们就开始使用它,如果我们要使用resolve以及reject还需要调用defer()方法。如下:

var deferred = $q.defer();

  而deferred(这个随便你取啥,不限制的)有三个可以使用的方法,以及一个处理promise的promise属性,慢慢道来,先说resolve方法。

defer()方法详解

  1.resolve(value):resolve函数一个value来执行deferred promise,表明promise对象由pending状态转为resolved。

deferred.resolve({name: "Ari", username: "@auser"});

  2.reject(reason):reject用一个reason来拒绝deferred promise,表明promise对象由pending状态转为rejected。

deferred.reject("Can't update user");

  3.notify(value):notify这个方法用于返回一个提醒信息。可在resolve或者reject之前可以被多次调用。

  除了三个方法以外,deferred还提供了一个promise属性,比如在我们封装服务最后return deferred.promise,这个属性能让我们去观察原来的promise对象的状态,比如成功了,被拒绝了,但无法修改deferred的内在状态。

  上面我们提的是promise的deffered对象,那promise有没有对应的状态监听方法呢,很明显是有点。

promise--then方法。

  如果把deffered理解为更改promise状态的方法,那么then就是对应监听promise不同状态的方法。

  我们在$http就直接使用过then方法,用于接受处理成功函数以及失败函数,这里我们保持前两者不变,加入了一个未改变状态的监听函数。

.then(successFn, errFn, notifyFn)

  划重点deffered的resolve将promise状态从pending改为了resolved,直白点,请求成功了,那么我们的then方法里面的第一个回调successFn可以监听到这个状态变化。

     当deffered的reject将promise状态从pending改为了rejected,直白点,请求被拒绝了,或者说失败了,那么我们得then方里面的第二个回调errFn可以监听到这个状态变化。

       当deffered还啥都没干,还是pedding状态,那么咱们then方法的第三个回调notifyFn就可以监听到。

promise--catch方法

.catch(errFn回调函数)

这个方法稍微好理解点,就只是个快捷方式,能让我们用.catch(function(reason){})取代上面then方法里面的err回调,单独用这个抓响应失败。

promise-finally方法

让你可以观察到一个 promise 是被执行还是被拒绝, 但这样做不用修改最后的 value值。需要注意的是,finally属于JavaScript的保留字,所以你要使用,得这样写:

promise['finally'](function() {});

$q的方法说明  

我们在前面说了$q.defer()方法,其实$q除了此方法外还有其它四个方法,下面一一列举。($q.refer()在上面已经提及,这里不再次做说明了)

$q.all(promises)

  如果我们想将多个promise合并成一个,可以使用$q.all()来进行合并,它有一个参数promises,promises可以是一个promise数组或者promise的hash。all()方法会返回单个promise,如果其中任意一个promise被拒绝,结果的promise也会被拒绝。

$q.reject(reason)

  这个方法会创建一个promise并以你提供的reason去拒绝它。它可以用于在一个promise链中转发拒绝的promise,类似于js中的throw。比如在js中我们可以捕获一个异常,并且抛出这个异常,那么在then链中$q.reject(reason)能帮你实现。

$q.when(value)

  when()函数把一个可能是值或者能接着then的promise包装成一个$q promise。这样我们就能处理一个可能是promise也可能不是promise的对象。

  wnen中的value是一个值或者是一个promise,但when()会最终返回一个promise,我们也可以正常的用promise方法去使用它。

本文采用资料:

Angular中的$q的形象解释及深入用法

 AngularJS 中的Promise --- $q服务详解

2018-6-15补充

准确来说,上面写的算是书籍以及概念的整理,我自己都觉得写的很差,毕竟自己整理完之后,$q使用场景是什么,何时使用,怎么使用还是比较模糊,也是在后续项目问题的解决中慢慢有了个清晰的思路,这里就做个补充。

耐心读完这点点文字,肯定有帮助。

1.$q是用来干嘛的

用来解决异步的,比如我现在有个需求,我要做个订单翻单的功能,就是在个人订单信息中找到已经买过的商品,点击翻单按钮,程序会自动取到这个产品的信息,再次提交到购物车,然后再将此购物车重新下单一次,也就是再购买一次。页面不会跳转,但是整个购物流程会全部跑一遍。

区别在于,我们一般购物操作是先在商品页面选商品,点击添加购物车按钮,没问题我们再点击结算按钮会跳到结算页面,不同的页面不同的点击按钮,这样程序是一步一步点击去执行的,先后顺序也很明确,但现在我这个翻单功能就点击一次按钮就得把整个购物流程跑一遍。

那么问题就来了,买东西需要添加购物车,此时会有个购物车独有ID,下单会依赖这个独有ID去结算这个购物车的商品,逻辑是一次性完成的,而添加购物车请求是异步的,我们怎么知道什么时候添加购物车完成了,可以取购物车的id了,异步问题就在这,状态很难获取。

我原本想的是先定义一个添加购物车函数,并在成功回调中返回添加成功购物车的信息,并利用这个信息去执行我接下来的下单操作,很遗憾,这个信息万年undefined,没法捕捉。难道在添加购物车成功回调中再去定义下单逻辑,那代码多丑陋。

那么我们就得利用$q来帮我们完成了。

2.$q怎么用

我是用$resouce和$q来完成这个需求的,要使用这两个东西,是需要依赖注入$resouce和$q的,这里就是一些基本概念了,假设我们相关依赖注入都做好了。

//假设有个翻单的service叫 reOrderService
//这是我添加购物车的操作
service.addToCart = function(data){
    var deferred = $q.defer();//生成deferred异步对象
    var url = xxxxx+"api/shoppingCart/items";
    var resourcemtd = $resource(url,{},{
        add:{//这是$resource对于http请求的方法定义,不用管
            method:'POST',
            isArray:false,
            headers:{
                Authorization:userToken
            }
        },
    });
    resourcemtd.add(data, function (resp) {
        if (resp.success) {;
            deferred.resolve(resp);//这个状态无法捕捉,利用$q改变deferred状态为执行成功
        }else{
            throw new Error('add to cart fail');
        }
    });
    return deferred.promise;//返回promise对象
}

//这是我的控制器操作  调用添加购物车函数,处理promise
var promise = reOrderService.addToCart(data);
promise.then(function(resp){//执行请求成功的回调
    var CardId = resp.shoppingCard.id //假设这是di
    //假设早service中有个下单函数叫addOrder 调用下单函数,传入购物车id
    reOrderService.addOrder(CardId);
})

因为不知道添加购物车成功回调什么时候才是成功,我们可以利用deferred.resolve()手动将它改成成功状态,同时返回一个了一个promise对象。

promise.then()属于promise对象的一个回调方法,第一个函数执行异步成功的函数,比如我们在第一个回调中去拿到添加购物车返回的数据,然后去调用下单操作。

如果我们不用$q,一般做法就是将下单请求写在添加购物车成功的回调中,但是这样代码会显得臃肿,我们还是希望每个功能模块的代码是独立的,这样更便于提升代码的可读性。

posted on 2018-04-09 19:23  听风是风  阅读(833)  评论(0编辑  收藏  举报