• 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,一般做法就是将下单请求写在添加购物车成功的回调中,但是这样代码会显得臃肿,我们还是希望每个功能模块的代码是独立的,这样更便于提升代码的可读性。

  • 相关阅读:
    使用python写天气预告
    beef配合ettercap批量劫持内网的浏览器
    html布局
    python 使用paramiko模块上传本地文件到ssh
    mysql一些函数的记录
    python与ssh交互
    html笔记4
    html笔记3
    html笔记2
    html笔记1
  • 原文地址:https://www.cnblogs.com/echolun/p/8760754.html
Copyright © 2020-2023  润新知