“JavaScript Promises和AngularJS $q Service”Part 1 (基础篇)
来源:互联网 发布:嘉合信网络信息技术 编辑:程序博客网 时间:2024/05/16 11:06
注:本文是译文,难免有错误或理解不足之处,请大家多多指正,大家也可挪步原文。由于本文讲解十分精彩,非常推荐大家查看原文,由于原文内容十分丰富,所以将其分为2部分,这是Part 1(基础篇),戳这里查看Part 2(教程篇)。
promise或deferred在异步编程中简单而又实用。维基上列了一些promise模式的实现要点。AngularJS根据Kris Kowal’s Q 定义了了自己的实现方式。在本文中我将介绍promises和使用promises的目的,并且提供一个有关AngularJS $q Service的使用教程。
使用Promise (Deferred)的目的
JavaScript中使用回调函数来通知一个操作“成功”或“失败”的状态。例如,Geolocation api为了获取当前位置需要一个成功回调函数和一个失败回调函数:
Geolocation api使用回调函数:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
另一个常见的例子是XMLHttpRequest(用来进行ajax调用)。XMLHttpRequest对象的onreadystatechange回调函数会在其readyState属性值改变时被调用:
XHR使用回调函数:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
JavaScript异步编程中,类似的例子不胜枚举,但是需要同步使用多个异步操作时使用回调函数的方式就不合适了。
嵌套噩梦(依次执行)
假设我们有N个异步方法:async1(success, failure), async2(success, failure), …, asyncN(success, failure),现在我们想要他们依次执行,后一个需要在前一个方法的success回调中才能执行,每一个函数都有success回调和failure回调:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
这样我们就遇到了著名的“嵌套噩梦”。即使上述代码有更好的表达方式,这样的代码也是难以阅读和维护的。
平行执行
假设我们有N个异步方法:async1(success, failure), async2(success, failure), …, asyncN(success, failure),并且我们想让他们平行执行,他们之间的执行是独立的,他们都执行完成后,我们弹出一条消息。每一个方法都有自己的success回调和failure回调:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
我们首先声明了一个计数器,并将其初始值设为异步函数的个数,即N。当一个函数执行后,我们将计数器减1,并检测其是否是最后一个执行的函数。这种方式并不容易实现和维护,尤其是当每个函数都给success回调传参数。在这种情况下,我们需要保存函数每一次执行的结果。
在上面两个例子中,异步函数执行时,我们都必须指定success回调的处理方式。换句话说,当我们使用回调函数时,异步操作需要保留他们的引用,但是保留的引用可能并不属于我们的业务逻辑,这就提高了模块和service之间的耦合度,使代码的重用和测试变的复杂。
promise和deferred是什么?
deferred表示一次异步操作的结果,它对外的接口用于表示此次异步操作的状态和结果。通过它还能获得其对应的promise实例。
promise提供了与deferred通信的接口,从而外部能够通过promise得知deferred操作的状态和结果。
当deferred被创建时,其状态为“挂起(pending)”,并没有任何结果,当其被resolve()或reject()后,其状态变为“处理成功(resolved)”或“处理失败(rejected)”。我们甚至可以在deferred刚刚被创建后就可以获得其对应的promise实例,并且使用其完成某些功能。不过这些功能只有在deferred被resolve()或reject()后才能生效。
即使在我们还没想好要在deferred被resolve()或reject()之后需要做什么工作,我们也可以使用promise轻易创建一个异步操作。这就实现了低耦合。由于一个异步操作完成后不知道下一步应该做什么,所以它必须在完成后发出信号。
deferred可以改变一个异步操作的状态,而promise只能获取和查看这些状态,并不能改变状态。这就是为什么一个函数通常应该返回promise而不是deferred的原因,这样做使得外部的业务逻辑不能干涉异步操作的过程和状态。
在不同的编程语言(JavaScript, Java, C++, Python等)和框架(NodeJS, jQuery等)中对于promise的实现均不相同。AngularJS在$q Service的基础上实现promise。
怎样使用deferred和promise
通过上文了解了promise和deferred的含义和用途后,下面让我们来了解一下如何使用它们。如上文所说,promise的实现多种多样,不同的实现方式具有不同的用法,这部分内容会使用AngularJS的实现方式(自备梯子),即$q Service。如果你使用的其他的实现方式也不用担心,我在本文中提到的大部分方法都是通用的,如果不是,总有相同功能的方法。
基本用法
首先,让我们先创建一个deferred:
- 1
- 1
再简单不过了,myFirstDeferred就是一个deferred,可以在异步操作结束后被resolve()或reject()。假设我们有个异步函数async(success, failure),参数为success回调和failure回调,当async函数执行完毕后,我们希望对myFirstDeferred进行resolve()或reject()操作:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
由于AngularJS的$q Service不依赖于上下文的执行环境,上面的代码可以简写成:
- 1
- 1
得到myFirstDeferred的promise实例,并对其分配成功回调和失败回调是非常简单的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
请注意,即使我们的异步函数async()还没有被执行,只要我们获得了deferred实例并得到其对应的promise,我们就可以对promise分配回调函数了:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
如果async()执行成功了(resolve被执行),上面代码中的两个“成功回调”都会被执行,async()执行失败了(reject被执行),上面代码中的两个“失败回调”也都会被执行。
封装异步操作的一个好方法是定义一个返回promise的函数,这样调用者可以按需要分配成功或失败回调,而不能干涉或改变异步操作的状态:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
直到现在,我们使用promise时还是分配了成功回调和失败回调,但其实也可以只分配成功回调或只分配失败回调:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
只传递成功回调给promise.then()就实现了“对promise只分配成功回调”,只传递失败回调给promise.catch()就实现了“对promise只分配失败回调”,而promise.catch()其实调用的是promise.then(null, errorCallback)。
而如果我们想要在deferred被resolve()和reject()后都做某些工作呢?我们可以使用promise.finally():
- 1
- 2
- 3
- 1
- 2
- 3
上述代码其实和下面的代码是等价的:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
值和promise的链式操作
设想我们有个异步函数async()返回一个promise,有下面一段有趣的代码:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
很容易理解,promise1.then()返回了另一个promise,这里命名为promise2,当promise1被处理(x作为参数传入),在promise1的成功回调中返回了x+1,这时promise2对应的处理函数将接收x+1作为参数。
再看一个类似的例子:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
当async()函数返回的promise被处理,其成功回调函数没有返回任何值,那此时promise2对应的处理函数将接收到undefined。
上面可以看出,promise可以进行链式合成,并且上一个promise的处理结果将作为下一个promise的处理参数。
为了演示效果,下面使用一个很傻的使用promise的例子(没有必要使用promise):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这个promise链起始于async(8)的调用,async(8)返回的promise的成功回调的参数为4,这个参数4以及对其处理结果会在所有promise的成功回调中传递,所以最后打印出的结果将是(8/2+1)*2-1,即为9。
如果我们的链中传递的不是值,而是另一个promise,会发生什么呢?假设现在我们有2个异步回调函数:async1()和async2(),它们都返回promise。来看下面的情形:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
不像上一个例子,这里async1()返回的promise的成功回调中执行了另一个异步操作并返回了一个promise:async2Promise。意料之中async1.then()返回的是一个promise,但是其结果要根据async2Promise的执行结果来看了,async2Promise可能执行成功回调,也可能执行失败回调。
因为因为async2()的参数使用的是async1()函数处理的值,并且async2()也返回一个promise,那上面的代码可以简写成:
- 1
- 2
- 1
- 2
下面是另一个例子,同样也仅是用作演示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
首先我们调用了async1(10),async1函数对参数进行处理后(即resolve()操作)在其返回的promise的成功回调中传入的参数x为20,并执行了async2(20),而async2函数中同样对参数进行处理后返回promise,此时async2返回的promise成功回调中传入的参数将为21,所以最后打印的结果为21。
上述代码可以用下面可读性更强的表达方式:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这样很容易看出执行的流程。
上面这些关于promise链的例子的结果是我们乐观处理的结果,即:我们假设promise执行的是都是成功的回调函数,即deferred都被resolve()了。但是如果deferred被reject()了,那整个promise链都将被rejected:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
很容易看出,最后打印的结果是Error: rejected for demonstration!,下面是一个关于promise链更高级的表示方法:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
这里,我们依次调用了async1(),async2(),async3()函数,如果其中某个函数被reject(),那么整个成功回调的链条将被打破,此时将执行handleReject()函数。而最后,不论怎样,freeResources()函数都会被执行。例如,如果async2()中被reject(),那么async3()将不会执行,handleReject()将接收async2()中reject()传入的参数(也可能不传参数)然后执行,最后执行freeResources()函数。
常用方法
AngularJS $q Service有一些非常有用的方法,这些方法在使用promise的时候会帮助很大。就像我开始所说的,其他的promise实现方式也有类似的方法,可能只是函数名不同。
有时我们需要返回一个被rejected的promise,我们可以使用$q.reject()返回一个带有参数的rejected promise:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
如果async()返回的promise的成功回调函数接收的参数(即deferred.resolve(value);中传递的参数)是合适的值(isSatisfied()函数返回true),那这个参数将被promise链接收并被resolve(),如果这个参数不是合适的值(isSatisfied()函数返回false),那$q.reject返回的rejected promise将被加入到promise链中,导致promise链被rejected。
如果async()返回的promise的失败回调函数接收的参数(即deferred.reject(param);中传递的参数)是合适的值(canRecovered()函数返回true),那么一个新值或promise将被加入到promise链中,如果这个参数不是合适的值(canRecovered()函数返回false),那$q.reject()返回的rejected promise将被加入到promise链中,导致promise链被rejected。
和$q.reject()类似的是$q.when(),有时我们需要返回一个resolved promise,我们可以使用$q.when()返回一个带参数的resolved promise:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
getDataFromBackend()函数用来从后台获取数据,不过在访问后台之前,先要在本地缓存中查找是否有相关的数据,如果有就使用$q.when()返回一个resolved promise。
$q.when()的功能不止于此,它还可以用来将第三方promise(如jQuery’s Deferred)封装成AngularJS对应的$q promise。
例如,jQuery的$.ajax()调用,返回的是jQuery的promise,可以使用如下方式转换成AngularJS的$q promise:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
有时候我们需要执行多个异步函数,不在意其执行顺序,只想在它们都执行完成后得到通知,可以使用$q.all(promiseArr)帮助我们实现这个功能。假设我们有N个异步方法:async1(), …, asyncN(),都返回promise,下面的代码只有当所有的操作都被resolved时才能打印出”done”:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
$q.all(promiseArr)当且仅当promiseArr数组里面所有的promise都被resolve时返回resoloved promise。注意,只要有一个promise被rejected,那得到的结果将是rejected promise。
到此为止,我们已经学习了怎样创建一个deferred,怎么对其进行resolve()和reject()操作,还学习怎样对其promise进行操作。我们还了解了一些AngularJS $q Service里的常用的方法,我想现在可以进行教程练习了。
请继续关注“JavaScript Promises和AngularJS $q Service” Part 2(教程篇)。
- “JavaScript Promises和AngularJS $q Service”Part 1 (基础篇)
- “JavaScript Promises和AngularJS $q Service”Part 1 (基础篇)
- “JavaScript Promises和AngularJS $q Service”Part 2 (教程篇)
- 理解 AngularJS $q service and promises
- AngularJS 用promises和$q处理异步调用
- JavaScript Promises
- JavaScript Promises
- JavaScript Promises
- javascript Promises
- Using AngularJS Promises
- AngularJS 的 $q 和 Promise
- AngularJs(Part 1)
- Javascript Promises Are Awesome
- 初识JavaScript Promises
- JavaScript Promises 介绍
- JavaScript 基础 Part 2
- JavaScript 基础 Part 3
- JavaScript 基础 Part 4
- php 序列化对象
- Qt Qt 绘制折线图 计算线段交点
- Retrofit2完全解析
- Cobbler批量安装操作系统
- 函数之间的相互调用
- “JavaScript Promises和AngularJS $q Service”Part 1 (基础篇)
- test
- 贝叶斯概率
- 1052.Linked List Sorting (25)
- 栈问题2(四则运算)
- 进程创建函数之fork()和vfork()
- shake_wu 的csdn博客开张了!
- script之正则表达式
- 流态区说法-《程序员的职业素养》