• home > webfront > ECMAS > angularjs >

    angularjs中promise的使用

    Author:zhoulujun@live.cn Date:

    了解Promisepromise是一种用异步的方式处理值的方法,promise是对象,代表了一个函数最终可能的返回值或者抛出的异常,在与远程对象打交道

    我们知道JavaScript语言的执行环境是“单线程”,所谓单线程,就是一次只能够执行一个任务,如果有多个任务的话就要排队,前面一个任务完成后才可以继续下一个任务。


    这种“单线程”的好处就是实现起来比较简单,容易操作;坏处就是容易造成阻塞,因为队列中如果有一个任务耗时比较长,那么后面的任务都无法快速执行,或导致页面卡在某个状态上,给用户的体验很差。


    当然JavaScript提供了“异步模式”去解决上述的问题,关于“异步模式”JavaScript提供了一些实现的方法。

    • 回调函数(callbacks)

    • 事件监听

    • Promise对象

    关于回调函数,大家应该都不陌生,比如下面的代码(注:引用Leancloud上面的一点代码):

     AV.User.logIn("myname", "mypass", {
                success: function(user) {            // Do stuff after successful login.
                },
                error: function(user, error) {            // The login failed. Check error to see why.
                 }
        });

    用户通过用户名和密码来进行登录,如果登陆成功的话,会在success这个模块进行处理,如果登陆失败的话,就会在error这个模块进行处理。


    当我们需要处理的任务不是很多的情况下,使用回调函数还是可以应付的,也没有太大的问题,但是当我们需要处理的任务比较多的时候,使用回调函数的弊端越来越明显了;首先,回调使得调用不一致,得不到保证;当依赖于其它回调时,它们篡改代码的流程,是调试变得异常艰难,每一步调用之后都需要显式的处理错误;最后,过多的回调使得代码的可读性和可维护性都变得很差,所以越来越多的程序员选择使用Promise去处理异步模式。 

    以前了解过Ajax的都能体会到回调的痛苦,同步的代码很容易调试,但是异步回调的代码,会让开发者陷入泥潭,无法跟踪,比如:

    funA(arg1,arg2,function(){
        funcB(arg1,arg2,function(){
            funcC(arg1,arg2,function(){
                 xxxx....
            })
        })   
    })

    本身嵌套就已经很不容易理解了,加上不知何时才触发回调,这就相当于雪上加霜了。


    但是有了Promise这种规范,它能帮助开发者用同步的方式,编写异步的代码,比如在AngularJS中可以使用这种方式:


    deferABC.resolve(xxx)
    .then(funcSuccess(){},funcError(){},funcNotify(){});

    当resolve内的对象成功执行,就会触发funcSuccess,如果失败就会触发funcError。有点类似

    deferABC.resolve(function(){
        Sunccess:funcSuccess,
        error:funcError,
        notify:funcNotify
    })

    再说的直白点,Promise就是一种对执行结果不确定的一种预先定义,如果成功,就xxxx;如果失败,就xxxx,就像事先给出了一些承诺。


    比如,小白在上学时很懒,平时总让舍友带饭,并且事先跟他说好了,如果有韭菜鸡蛋就买这个菜,否则就买西红柿炒鸡蛋;无论买到买不到都要记得带包烟。

    小白让舍友带饭()
    .then(韭菜鸡蛋,西红柿炒鸡蛋)
    .finally(带包烟)

    Promise是什么

    Promise是一种异步方式处理值(或者非值)的方法,promise是对象,代表了一个函数最终可能的返回值或者抛出的异常。


    在与远程对象打交道时,Promise会非常有用,可以把它们看作远程对象的一个代理。


    点击下面的链接可以查看Promise更多的信息

    使用Promise的理由

    • 使用Promise可以让我们逃脱回调地狱,使我们的代码看起来像是同步的那样。

    • 可以在程序中的任何位置捕捉错误,并且绕过依赖于程序异常的的后续代码,获得功能组合和错误冒泡的能力,最重要的是保持了异步运行的能力。

    • 使我们的代码的可读性与可维护性都变得很好。

    如何在AngularJS中使用Promise

    要在AngularJS中使用Promise,要使用AngularJS的内置服务$q

    • $q服务受到Kris KowalQ库的启发,所以类似于那个库,但是并没有包含那个库的所用功能,q服务是AngularJS中自己封装实现的一种Promise实现,相对与Kris Kwal's Q要轻量级的多。

    • $q是跟AngularJS$rootScope模板集成的,所以在AngularJS中执行和拒绝都很快。

    • $q promise是跟AngularJS模板引擎集成的,这意味着在视图中找到任何Promise都会在视图中被执行或者拒绝。

    我们可以先使用$qdefer()方法创建一个deferred对象,然后通过deferred对象的promise属性,将这个对象变成一个promise对象;这个deferred对象还提供了三个方法,分别是resolve(),reject(),notify()

    先介绍一下$q常用的几个方法:

    1)defer() 创建一个deferred对象,这个对象可以执行几个常用的方法,比resolve,reject,notify等

    2)all() 传入Promise的数组,批量执行,返回一个promise对象

    3)when() 传入一个不确定的参数,如果符合Promise标准,就返回一个promise对象。

    在Promise中,定义了三种状态:等待状态,完成状态,拒绝状态。

    关于状态有几个规定:

    • 1 状态的变更是不可逆的

    • 2 等待状态可以变成完成或者拒绝

    defer()方法

    在$q中,可以使用resolve方法,变成完成状态;使用reject方法,变成拒绝状态。

    下面看看 $q的简单使用:

    {{test}}
    var myAppModule = angular.module("myApp",[]);
       myAppModule.controller("myctrl",["$scope","$q",function($scope, $ q ){
                $scope.test = 1;//这个只是用来测试angularjs是否正常的,没其他的作用
                var defer1 = $q.defer();
                var promise1 = defer1.promise;
    
                promise1
                .then(function(value){
                    console.log("in promise1 ---- success");
                    console.log(value);
                },function(value){
                    console.log("in promise1 ---- error");
                    console.log(value);
                },function(value){
                    console.log("in promise1 ---- notify");
                    console.log(value);
                })
                .catch(function(e){
                    console.log("in promise1 ---- catch");
                    console.log(e);
                })
                .finally(function(value){
                    console.log('in promise1 ---- finally');
                    console.log(value);
                });
    
                defer1.resolve("hello");
                // defer1.reject("sorry,reject");
             }]);

    其中defer()用于创建一个deferred对象,defer.promise用于返回一个promise对象,来定义then方法。then中有三个参数,分别是成功回调、失败回调、状态变更回调。


    其中resolve中传入的变量或者函数返回结果,会当作第一个then方法的参数。then方法会返回一个promise对象,因此可以写成

    xxxx
    .then(a,b,c)
    .then(a,b,c)
    .then(a,b,c)
    .catch()
    .finally()

    继续说说上面那段代码,then...catch...finally可以想想成java里面的try...catch...finally。


    下面我们来通过代码逐步地将上面的功能都实现,毕竟说得再多,不如你实实在在地把它们敲成代码去实现。


    其中defer()用于创建一个deferred对象,defer.promise用于返回一个promise对象,来定义then方法。then中有三个参数,分别是成功回调、失败回调、状态变更回调。


    其中resolve中传入的变量或者函数返回结果,会当作第一个then方法的参数。then方法会返回一个promise对象,因此可以写成

    xxxx
    .then(a,b,c)
    .then(a,b,c)
    .then(a,b,c)
    .catch()
    .finally()


    继续说说上面那段代码,then...catch...finally可以想想成java里面的try...catch...finally。


    all()方法

    这个all()方法,可以把多个primise的数组合并成一个。当所有的promise执行成功后,会执行后面的回调。回调中的参数,是每个promise执行的结果。

    当批量的执行某些方法时,就可以使用这个方法。


               var funcA = function(){
                    console.log("funcA");
                    return "hello,funA";
                }
                var funcB = function(){
                    console.log("funcB");
                    return "hello,funB";
                }
                $q.all([funcA(),funcB()])
                .then(function(result){
                    console.log(result);
                });

    执行的结果:

    funcA
    funcBArray [ "hello,funA", "hello,funB" ]

    when()方法

    when方法中可以传入一个参数,这个参数可能是一个值,可能是一个符合promise标准的外部对象。

                var funcA = function(){
                    console.log("funcA");
                    return "hello,funA";
                }
                $q.when(funcA())
                .then(function(result){
                    console.log(result);
                });

    当传入的参数不确定时,可以使用这个方法。

    hello,funA


    再来详细地侃PROMISE 

    我们先通过一个同步的例子来创建一个promise对象。

    angular.module("MyApp", [])
    .controller("MyController", ["$scope", "$q", function ($scope, $q) {
          $scope.flag = true;
          $scope.handle = function () {          
                  var deferred = $q.defer();           
                  var promise = deferred.promise;
    
                promise.then(function (result) {
                    alert("Success: " + result);
                }, function (error) {
                    alert("Fail: " + error);
                });            if ($scope.flag) {
                    deferred.resolve("you are lucky!");
                } else {
                    deferred.reject("sorry, it lost!");
                }
            }
    }]);

    我们来详细的分析一下上面的代码,我们在html页面上添加了一个checkbox,一个button目的是为了当我们选中checkbox和不选中checkbox时,点击下面的按钮会弹出不同的内容。

    var deferred = $q.defer()这段代码创建了一个deferred对象,我们然后利用var promise = deferred.promise创建了一个promise对象。

    我们给给promisethen方法传递了两个处理函数,分别处理当promise被执行的时候以及promise被拒绝的时候所要进行的操作。

    下面的一个if(){}else{}语句块,包含执行和拒绝deferred promise,如果$scope.flagtrue,那么我们就会执行deferred promise,然后我们给promise传递一个值,也可能是一个对象,表明promise执行的结果。如果$scope.flagfalse,那么我们就会拒绝deferred promise,然后我们给promise传递一个值,也可能是一个对象,表明promise被拒绝的原因。

    现在回过头来看看,promisethen方法,如果promise被执行,那么它的参数中的第一个函数的result就代表了"you are lucky!"

    我们暂时用的是同步的模式,为的是能够说明问题,后面将会使用异步的方法。

    到这里我们可以了解一下$qdefer()方法创建的对象具有哪些方法

    • resolve(value):用来执行deferred promisevalue可以为字符串,对象等。

    • reject(value):用来拒绝deferred promisevalue可以为字符串,对象等。

    • notify(value):获取deferred promise的执行状态,然后使用这个函数来传递它。

    • then(successFunc, errorFunc, notifyFunc):无论promise是成功了还是失败了,当结果可用之后,then都会立刻异步调用successFunc,或者'errorFunc',在promise被执行或者拒绝之前,notifyFunc可能会被调用0到多次,以提供过程状态的提示。

    • catch(errorFunc)

    • finally(callback)


    通过使用then进行链式请求

    我们通过使用then方法来进行链式调用,这样做的好处是,无论前一个任务或者说then函数是被执行或者拒绝了都不会影响后面的then函数的运行。

    我们可以通过then创建一个执行链,它允许我们中断基于更多功能的应用流程,可以借此导向不同的的结果,这个中断可以让我们在执行链的任意时刻暂停后者推迟promise的执行。

    Test2

    HTML代码

                成功        
                                                        {{status}}                        点击我

    JS代码:

            angular.module("MyApp", [])
            .controller("MyController", ["$scope", "$q", function ($scope, $q) {
                $scope.flag = true;
                $scope.handle = function () {            var deferred = $q.defer();            var promise = deferred.promise;
    
                promise.then(function (result) {
                    result = result + "you have passed the first then()";
                    $scope.status = result;                return result;
                }, function (error) {
                    error = error + "failed but you have passed the first then()";
                    $scope.status = error;                return error;
                }).then(function (result) {
                    alert("Success: " + result);
                }, function (error) {
                    alert("Fail: " + error);
                })            if ($scope.flag) {
                    deferred.resolve("you are lucky!");
                } else {
                    deferred.reject("sorry, it lost!");
                }
            }
    }]);

    我们在Part1代码的基础上添加了一些代码,在原来的promise的链条上新添加了一个then()处理函数,目的就是为了创建一个执行连,看看在这条执行连上,promise是如何被执行的。

    需要注意的一点是,在第一个then()方法中,我们在第一个successFunc函数中将result的值进行了改变,在第二个errorFunc函数中对error的值也进行了改变。

    因为这个promise对象是贯穿整个执行链条的,所以在第一个then()方法中对其值进行改变必然会反映到后面的then()方法中

    一个异步模式的实例

    Test3

    第三个例子,我们创建了一个服务,然后在这个服务中创建了一个promise,服务的目的就是为了拉取github上面关于angularjs一些pull的数据,详细的代码可以看下面

    下面的例子包含的部分有点多,因为我是在以前的例子上做的改动,大家可以只看promise这部分。

    目录结构:

    MyApp
    js
    app.js
    controller.js
    service.js
    views
    home.html

    js/app.js

    angular.module("MyApp", ["ngRoute","MyController", "MyService"])
    .config(["$routeProvider", function($routeProvider){
        $routeProvider
        .when('/',{
            templateUrl: "views/home.html",
            controller: "IndexController"
        });
    }]);

    js/controller.js

    angular.module("MyController", [])
        .controller("IndexController", ["$scope", "githubService",                                function($scope, githubService){
            $scope.name = "dreamapple";
            $scope.show = true;
            githubService.getPullRequests().then(function(result){
                $scope.data = result;
            },function(error){
                $scope.data = "error!";
            },function(progress){
                $scope.progress = progress;
                $scope.show = false;
            });
        }]);

    js/service.js

        angular.module("MyService", [])
        .factory('githubService', ["$q", "$http", function($q, $http){        var getPullRequests = function(){        var deferred = $q.defer();        var promise = deferred.promise;        var progress;
            $http.get("https://api.github.com/repos/angular/angular.js/pulls")
            .success(function(data){            var result = [];            for(var i = 0; i < data.length; i++){
                    result.push(data[i].user);
                    progress = (i+1)/data.length * 100;
                    deferred.notify(progress);
                }
                deferred.resolve(result);
                })
            .error(function(error){
                deferred.reject(error);
            });        return promise;
        }    return {
            getPullRequests: getPullRequests
        };
    }]);

    views/home.html

    <h1>{{name}}</h1>
    <h2>Progress: {{progress}}</h2>
    <h3 ng-show="show">Please wait a moment...</h3>
    <p ng-repeat="person in data">{{person.login}}</p>

    index.html

    <!-- 不把下面的注释掉会出现问题,我是指上传到segmentfault上 -->
    <!-- <head>
        <meta charset="UTF-8">
        <title>Route</title>
        <script src="http://cdn.bootcss.com/angular.js/1.4.0-rc.1/angular.js"></script>
        <script src="../node_modules/angular-route/angular-route.js"></script>
        <script src="js/app.js"></script>
        <script src="js/controller.js"></script>
        <script src="js/service.js"></script>
    </head> -->
    <body ng-app="MyApp">
        <header>
            <h1>Header</h1>
            <hr/>
        </header>
        <div ng-view>
        </div>
        <footer>
            <hr/>
            <h1>Footer</h1>
        </footer>
    </body>

    终于可以伸个懒腰了,关于$q还有一个方法,大家有兴趣的话可以自己看看相关资料,我这里就不多说了。。。

    参考文章:

    https://segmentfault.com/a/1190000002788733

    http://www.cnblogs.com/xing901022/p/4928147.html




    转载本站文章《angularjs中promise的使用》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/angularjs/2016_1201_7910.html