angularJS $q and promise

网上一篇生动生动讲述了promise异步编程问题原文地址http://www.ngnice.com/posts/126ee9cf6ddb68

promise不是angular首创的,作为一种编程模式,它出现在……1976年,比js还要古老得多。promise全称是

Futures andpromises。具体的可以参见 http://en.wikipedia.org/wiki/Futures_and_promises 。而在javascript世界

中,一个广泛流行的库叫做Q 地址是https://github.com/kriskowal/q 而angular中的$q就是从它引入的。promise解决

的是异步编程的问题,对于生活在同步编程世界中的程序员来说,它可能比较难于理解,这也构成了angular入门门槛

之一,本文将用生活中的一个例子对此做一个形象的讲解。


假设有一个家具厂,而它有一个VIP客户张先生。

有一天张先生需要一个豪华衣柜,于是,他打电话给家具厂说我需要一个衣柜,回头做好了给我送来,这个操作

就叫$q.defer,也就是延期,因为这个衣柜不是现在要的,所以张先生这是在发起一个可延期的请求。同时,家具厂

给他留下了一个回执号,并对他说:我们做好了会给您送过去,放心吧。这叫做promise,也就是承诺。这样,这个

defer算是正式创建了,于是他把这件事记录在自己的日记上,并且同时记录了回执号,这叫做deferred,也就是已延

期事件。


现在,张先生就不用再去想着这件事了,该做什么做什么,这就是“异步”的含义。


假设家具厂在一周后做完了这个衣柜,并如约送到了张先生家(包邮哦,亲),这就叫做deferred.resolve(衣

柜),也就是“已解决”。而这时候张先生只要签收一下这个(衣柜)参数就行了,当然,这个“邮包”中也不一定只有衣

柜,还可以包含别的东西,比如厂家宣传资料、产品名录等。整个过程中轻松愉快,谁也没等谁,没有浪费任何时

间。

假设家具厂在评估后发现这个规格的衣柜我们做不了,那么它就需要deferred.reject(理由),也就是“拒绝”。拒绝

没有时间限制,可以发生在给出承诺之后的任何时候,甚至可能发生在快做完的时候。而且拒绝时候的参数也不仅仅

限于理由,还可以包含一个道歉信,违约金之类的,总之,你想给他什么就给他什么,如果你觉得不会惹恼客户,那

么不给也没关系。假设家具厂发现,自己正好有一个符合张先生要求的存货,它就可以用$q.when(现有衣柜)来把这

个承诺给张先生,这件事就立即被解决了,皆大欢喜,张先生可不在乎你是从头做的还是现有的成品,只会惊叹于你

们的效率之高。

假设这个家具厂对客户格外的细心,它还可能通过deferred.notify(进展情况)给张先生发送进展情况的“通知”。

这样,整个异步流程就圆满完成,无论成功或者失败,张先生都没有往里面投入任何额外的时间成本。


好,我们再扩展一下这个故事:

张先生这次需要做一个桌子,三把椅子,一张席梦思,但是他不希望今天收到个桌子,明天收到个椅子,后天又

得签收一次席梦思,而是希望家具厂做好了之后一次性送过来,但是他下单的时候又是分别下单的,那么他就可以重

新跟家具厂要一个包含上述三个承诺的新承诺,这就是$q.all(桌子承诺,椅子承诺,席梦思承诺),这样,他就不用再

关注以前的三个承诺了,直接等待这个新的承诺完成,到时候只要一次性签收了前面的这些承诺就行了。

ngularJS提供了一个内置Service $q,它提供了一种承诺/延后(promise/deferred),可以保证我们的调用代码

一定能够拿到数据。当然,我们可以猜到,最后去服务器取数据的方式肯定是异步的。只不过这个服务提供了表面上

是同步访问的API,当数据获取成功之后,自动将数据提供给调用的代码。

$q – A promise/deferred implementation inspired by Kris Kowal’s Q.The CommonJS Promise proposal

describes a promise as an interface for interacting with an object that representsthe result of an action that is

performed asynchronously,and may or may not be finished at any given point in time.

话不多说,上代码看看:

1. 创建一个Service,去服务器读取数据:

<span style="font-size:18px;"><span style="font-size:18px;">// $q 是内置服务,所以可以直接使用  
ngApp.factory('UserInfo',['$http','$q',function ($http,$q) {  
  return {  
    query : function() {  
      var deferred = $q.defer(); // 声明延后执行,表示要去监控后面的执行  
      $http({method: 'GET',url: 'scripts/mine.json'}).  
      success(function(data,status,headers,config) {  
        deferred.resolve(data);  // 声明执行成功,即http请求数据成功,可以返回数据了  
      }).  
      error(function(data,config) {  
        deferred.reject(data);   // 声明执行失败,即服务器返回错误  
      });  
      return deferred.promise;   // 返回承诺,这里并不是最终数据,而是访问最终数据的API  
    } // end query  
  };  
}]);  </span></span>

2. 在Controller上(以同步方式)使用这个Service:

<span style="font-size:18px;">angular.module('ngApp')  
  .controller('MainCtrl',['$scope','UserInfo',function ($scope,UserInfo) { // 引用我们定义的UserInfo服务  
    var promise = UserInfo.query(); // 同步调用,获得承诺接口  
    promise.then(function(data) {  // 调用承诺API获取数据 .resolve  
        $scope.user = data;  
    },function(data) {  // 处理错误 .reject  
        $scope.user = {error: '用户不存在!'};  
    });  
  }]);</span>


了解Promise

在谈论Promise之前我们要了解一下一些额外的知识;我们知道JavaScript语言的执行环境是“单线程”,所谓单

线程,就是一次只能够执行一个任务,如果有多个任务的话就要排队,前面一个任务完成后才可以继续下一个任务。

这种“单线程”的好处就是实现起来比较简单,容易操作;坏处就是容易造成阻塞,因为队列中如果有一个任务耗时比

较长,那么后面的任务都无法快速执行,或导致页面卡在某个状态上,给用户的体验很差。当然JavaScript提供了“异

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

  • 回调函数(callbacks)
  • 事件监听
  • Promise对象

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

<span style="font-size:18px;">    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.
             }
    });</span>



用户通过用户名和密码来进行登录,如果登陆成功的话,会在success这个模块进行处理,如果登陆失败的话,
就会在error这个模块进行处理。
当我们需要处理的任务不是很多的情况下,使用回调函数还是可以应付的,也没有太大的问题,但是当我们需要

处理的任务比较多的时候,使用回调函数的弊端越来越明显了;首先,回调使得调用不一致,得不到保证;当依

赖于其它回调时,它们篡改代码的流程,是调试变得异常艰难,每一步调用之后都需要显式的处理错误;最后,过多

的回调使得代码的可读性和可维护性都变得很差,所以越来越多的程序员选择使用Promise去处理异步模式。

关于Promise我们会在下面进行详细的说明。


Promise是什么

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

抛出的异常。

在与远程对象打交道时,Promise会非常有用,可以把它们看作远程对象的一个代理。
点击下面的链接可以查看Promise更多的信息

Promses/A+
Promises/A

使用Promise的理由

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

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

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

如何在AngularJS中使用Promise

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

  • $q服务受到Kris Kowal的Q库的启发,所以类似于那个库,但是并没有包含那个库的所用功能。
  • $q是跟AngularJS的$rootScope模板集成的,所以在AngularJS中执行和拒绝都很快。
  • $q promise是跟AngularJS模板引擎集成的,这意味着在视图中找到任何Promise都会在视图中被执行或者拒绝。
  • 我们可以先使用$q的defer()方法创建一个deferred对象,然后通过deferred对象的promise属性,将这个对象变成一个promise对象;这个deferred对象还提供了三个方法,分别是resolve(),reject(),notify()。
下面我们来通过代码逐步地将上面的功能都实现,毕竟说得再多,不如你实实在在地把它们敲成代码去实现。


Test1
我们先通过一个同步的例子来创建一个promise对象。
HTML代码:

<span style="font-size:18px;"><div ng-app="MyApp">
    <div ng-controller="MyController">
        <label for="flag">成功
        <input id="flag" type="checkbox" ng-model="flag" /><br/>
        </label>
        <hr/>
        <button ng-click="handle()">点击我</button>
    </div>
</div></span>
JS代码:

<span style="font-size:18px;">angular.module("MyApp",[])
.controller("MyController",["$scope","$q",$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!");
            }
        }
}]);</span>


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

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

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

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

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

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

到这里我们可以了解一下$q的defer()方法创建的对象具有哪些方法
  • resolve(value):用来执行deferred promise,value可以为字符串,对象等。
  • reject(value):用来拒绝deferred promise,value可以为字符串,对象等。
  • 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代码:

<span style="font-size:18px;"><div ng-app="MyApp">
    <div ng-controller="MyController">
        <label for="flag">成功
        <input id="flag" type="checkbox" ng-model="flag" /><br/>
        </label>
        <div ng-cloak>
            {{status}}
        </div>
        <hr/>
        <button ng-click="handle()">点击我</button>
    </div>
</div></span>

JS代码:

<span style="font-size:18px;">        angular.module("MyApp",[])
        .controller("MyController",$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!");
            }
        }
}]);</span>


我们在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

index.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","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/servce.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

<span style="font-size:18px;"><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></span>

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>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


ANGULAR.JS:NG-SELECTANDNG-OPTIONSPS:其实看英文文档比看中文文档更容易理解,前提是你的英语基础还可以。英文文档对于知识点讲述简明扼要,通俗易懂,而有些中文文档读起来特别费力,基础差、底子薄的有可能一会就会被绕晕了,最起码英文文档中的代码与中文文档中的代码是一致的,但知识点讲述实在是差距太大。Angular.jshasapowerfuldire
AngularJS中使用Chart.js制折线图与饼图实例  Chart.js 是一个令人印象深刻的 JavaScript 图表库,建立在 HTML5 Canvas 基础上。目前,它支持6种图表类型(折线图,条形图,雷达图,饼图,柱状图和极地区域区)。而且,这是一个独立的包,不依赖第三方 JavaScript 库,小于 5KB。   其中用到的软件:   Chart.js框架,版本1.0.2,一
IE浏览器兼容性后续前言 继续尝试解决IE浏览器兼容性问题,结局方案为更换jquery、angularjs、IE的版本。 1.首先尝试更换jquery版本为1.7.2 jquery-1.9.1.js-->jquery-1.7.2.js--> jquery2.1.4.js 无效 2.尝试更换IE版本IE8 IE11-
Angular实现下拉菜单多选写这篇文章时,引用文章地址如下:http://ngmodules.org/modules/angularjs-dropdown-multiselecthttp://dotansimha.github.io/angularjs-dropdown-multiselect/#/AngularJSDropdownMultiselectThisdire
在AngularJS应用中集成科大讯飞语音输入功能前言 根据项目需求,需要在首页搜索框中添加语音输入功能,考虑到科大讯飞语音业务的强大能力,遂决定使用科大讯飞语音输入第三方服务。软件首页截图如下所示: 涉及的源代码如下所示: //语音识别$rootScope.startRecognize = function() {var speech;
Angular数据更新不及时问题探讨前言 在修复控制角标正确变化过程中,发觉前端代码组织层次出现了严重问题。传递和共享数据时自己使用的是rootScope,为此造成了全局变量空间的污染。根据《AngularJs深度剖析与最佳实践》,如果两个控制器的协作存在大量的数据共享和交互可以利用Factory等服务的“单例”特性为它们注入一个共享对象来传递数据。而自己在使用rootScope
HTML:让表单、文本框只读,不可编辑的方法有时候,我们希望表单中的文本框是只读的,让用户不能修改其中的信息,如使中国">的内容,"中国"两个字不可以修改。实现的方式归纳一下,有如下几种。方法1:onfocus=this.blur()中国"onfocus=this.blur()>方法2:readonly中国"readonly>中国"readonly="tru
在AngularJS应用中实现微信认证授权遇到的坑前言 项目开发过程中,移动端新近增加了一个功能“微信授权登录”,由于自己不是负责移动端开发的,但最后他人负责的部分未达到预期效果。不能准确实现微信授权登录。最后还得靠自己做进一步的优化工作,谁让自己是负责人呢?原来负责人就是负责最后把所有的BUG解决掉。 首先,熟悉一下微信授权部分的源代码,如下所示:
AngularJS实现二维码信息的集成思路需求 实现生成的二维码包含订单详情信息。思路获取的内容数据如下: 现在可以获取到第一级数据,第二级数据data获取不到。利用第一级数据的获取方法获取不到第二级数据。for(i in data){alert(i); //获得属性 if(typeof(data[i]) == "o
Cookie'data'possiblynotsetoroverflowedbecauseitwastoolarge(5287>4096bytes)!故事起源 项目开发过程中遇到以上问题,刚开始以为只是个警告,没太在意。后来发现直接影响到了程序的执行效果。果断寻找解决方法。问题分析 根据Chrome浏览器信息定位,显示以下代码存在错误:
AngularJS控制器controller之间如何通信angular控制器通信的方式有三种:1,利用作用域继承的方式。即子控制器继承父控制器中的内容2,基于事件的方式。即$on,$emit,$boardcast这三种方式3,服务方式。写一个服务的单例然后通过注入来使用利用作用域的继承方式由于作用域的继承是基于js的原型继承方式,所以这里分为两种情况,当作用域上面的值
AngularJS路由问题解决遇到了一个棘手的问题:点击优惠详情时总是跳转到药店详情页面中去。再加一层地址解决了,但是后来发现问题还是来了:Couldnotresolve'yhDtlMaintain/yhdetail'fromstate'yhMaintain'药店详情http://192.168.1.118:8088/lmapp/index.html#/0优惠券详情
书海拾贝之特殊的ng-src和ng-href在说明这两个指令的特殊之前,需要先了解一下ng的启动及执行过程,如下:1)浏览器加载静态HTML文件并解析为DOM;2)浏览器加载angular.js文件;3)angular监听DOMContentLoaded事件,监听到时开始启动;4)angular寻找ng-app指令,确定作用范围;
angularjs实现页面跳转并进行参数传递Angular页面传参有多种办法,我在此列举4种最常见的:1.基于ui-router的页面跳转传参(1)在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个producers,点击其中一个目标,页面跳转到对应的producer页,同时将producerId
AngularJS实现表格数据的编辑,更新和删除效果实现首先,我们先建立一些数据,当然你可以从你任何地方读出你的数据var app = angular.module('plunker', ['ui.bootstrap']);app.controller('MainCtrl', function($scope) { $scope.name = 'World'; $sc
ANGULAR三宗罪之版本陷阱      坑!碰到个大坑,前面由于绑定日期时将angular版本换为angular-1.3.0-beta.1时,后来午睡后,登录系统,发现无论如何都登陆不进去了,经过调试,发现数据视图已经无法实现双向绑定了。自己还以为又碰到了“僵尸程序”了呢,对比药店端的程序发现并没有什么不同之处。后来自己经过一番思索才隐约感觉到是不是angular的版本造成的,将版本换为之前
JS实现分页操作前言 项目开发过程中,进行查询操作时有可能会检索出大量的满足条件的查询结果。在一页中显示全部查询结果会降低用户的体验感,故需要实现分页显示效果。受前面“JS实现时间选择插件”的启发,自己首先需要查看一下HTML5能否实现此效果。 整了半天,不管是用纯CSS3也好,还是用tmpagination.js还是bootstrap组件也好,到最后自己静下心来理
浏览器兼容性解决之道前言 浏览器兼容性一直是前端开发中不得不面对的一个问题。而最突出的就是IE。对绝大多数公司来说,兼容IE6的性价比已经很低,而IE7则几乎已经绝迹。所以,常见的兼容性下限是IE8。这也正是Angular1.2x的兼容性目标,Angular团队声明:Angular的持续集成服务器会在IE8下运行所有的测试。但这些测试不会运行在IE7及以下版本,它们也不会保证An
JS利用正则表达式校验手机号绪 由于项目需求,需要在前端实现手机号码的校验。当然了,对于基本的格式校验应该放在客户端进行,而不需要再将待校验的手机号发送至服务端,在服务端完成校验,然后将校验结果返回给客户端,客户端根据返回的结果再进行进一步的处理。如此反而复杂化了处理过程。 其实,处于安全考虑,应该在服务端进行二次校验。以下为在客户端的JS中校验手机号码格式
基于项目实例解析ng启动加载过程前言 在AngularJS项目开发过程中,自己将遇到的问题进行了整理。回过头来总结一下angular的启动过程。 下面以实际项目为例进行简要讲解。1.载入ng库2.等待,直到DOM树构造完毕。3.发现ng-app,自动进入启动引导阶段。4.根据ng-app名称找到相应的路由。5.加载默认地址。6.Js顺序执行,加载相应模版页sys_tpls/