“JavaScript Promises和AngularJS $q Service”Part 1 (基础篇)
2017-02-26 23:03
435 查看
注:本文是译文,难免有错误或理解不足之处,请大家多多指正,大家也可挪步原文。由于本文讲解十分精彩,非常推荐大家查看原文,由于原文内容十分丰富,所以将其分为2部分,这是Part 1(基础篇),戳这里查看Part
2(教程篇)。
promise或deferred在异步编程中简单而又实用。维基上列了一些promise模式的实现要点。AngularJS根据Kris
Kowal’s Q 定义了了自己的实现方式。在本文中我将介绍promises和使用promises的目的,并且提供一个有关AngularJS $q Service的使用教程。
JavaScript中使用回调函数来通知一个操作“成功”或“失败”的状态。例如,Geolocation api为了获取当前位置需要一个成功回调函数和一个失败回调函数:
Geolocation api使用回调函数:
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使用回调函数:
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回调:
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回调:
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之间的耦合度,使代码的重用和测试变的复杂。
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。
通过上文了解了promise和deferred的含义和用途后,下面让我们来了解一下如何使用它们。如上文所说,promise的实现多种多样,不同的实现方式具有不同的用法,这部分内容会使用AngularJS的实现方式(自备梯子),即$q
Service。如果你使用的其他的实现方式也不用担心,我在本文中提到的大部分方法都是通用的,如果不是,总有相同功能的方法。
首先,让我们先创建一个deferred:
1
再简单不过了,myFirstDeferred就是一个deferred,可以在异步操作结束后被resolve()或reject()。假设我们有个异步函数async(success, failure),参数为success回调和failure回调,当async函数执行完毕后,我们希望对myFirstDeferred进行resolve()或reject()操作:
2
3
4
5
1
2
3
4
5
由于AngularJS的$q Service不依赖于上下文的执行环境,上面的代码可以简写成:
1
得到myFirstDeferred的promise实例,并对其分配成功回调和失败回调是非常简单的:
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
请注意,即使我们的异步函数async()还没有被执行,只要我们获得了deferred实例并得到其对应的promise,我们就可以对promise分配回调函数了:
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的函数,这样调用者可以按需要分配成功或失败回调,而不能干涉或改变异步操作的状态:
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时还是分配了成功回调和失败回调,但其实也可以只分配成功回调或只分配失败回调:
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():
2
3
1
2
3
上述代码其实和下面的代码是等价的:
2
3
4
1
2
3
4
设想我们有个异步函数async()返回一个promise,有下面一段有趣的代码:
2
3
4
1
2
3
4
很容易理解,promise1.then()返回了另一个promise,这里命名为promise2,当promise1被处理(x作为参数传入),在promise1的成功回调中返回了x+1,这时promise2对应的处理函数将接收x+1作为参数。
再看一个类似的例子:
2
3
4
5
1
2
3
4
5
当async()函数返回的promise被处理,其成功回调函数没有返回任何值,那此时promise2对应的处理函数将接收到undefined。
上面可以看出,promise可以进行链式合成,并且上一个promise的处理结果将作为下一个promise的处理参数。
为了演示效果,下面使用一个很傻的使用promise的例子(没有必要使用promise):
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。来看下面的情形:
2
3
4
5
6
1
2
3
4
5
6
不像上一个例子,这里async1()返回的promise的成功回调中执行了另一个异步操作并返回了一个promise:async2Promise。意料之中async1.then()返回的是一个promise,但是其结果要根据async2Promise的执行结果来看了,async2Promise可能执行成功回调,也可能执行失败回调。
因为因为async2()的参数使用的是async1()函数处理的值,并且async2()也返回一个promise,那上面的代码可以简写成:
2
1
2
下面是另一个例子,同样也仅是用作演示:
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。
上述代码可以用下面可读性更强的表达方式:
2
3
4
5
6
7
1
2
3
4
5
6
7
这样很容易看出执行的流程。
上面这些关于promise链的例子的结果是我们乐观处理的结果,即:我们假设promise执行的是都是成功的回调函数,即deferred都被resolve()了。但是如果deferred被reject()了,那整个promise链都将被rejected:
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链更高级的表示方法:
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:
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:
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:
2
3
4
5
6
1
2
3
4
5
6
有时候我们需要执行多个异步函数,不在意其执行顺序,只想在它们都执行完成后得到通知,可以使用$q.all(promiseArr)帮助我们实现这个功能。假设我们有N个异步方法:async1(), …, asyncN(),都返回promise,下面的代码只有当所有的操作都被resolved时才能打印出”done”:
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(教程篇)。
2(教程篇)。
promise或deferred在异步编程中简单而又实用。维基上列了一些promise模式的实现要点。AngularJS根据Kris
Kowal’s Q 定义了了自己的实现方式。在本文中我将介绍promises和使用promises的目的,并且提供一个有关AngularJS $q Service的使用教程。
使用Promise (Deferred)的目的
JavaScript中使用回调函数来通知一个操作“成功”或“失败”的状态。例如,Geolocation api为了获取当前位置需要一个成功回调函数和一个失败回调函数:Geolocation api使用回调函数:
function success(position) { var coords = position.coords; console.log('Your current position is ' + coords.latitude + ' X ' + coords.longitude); } function error(err) { console.warn('ERROR(' + err.code + '): ' + err.message); } navigator.geolocation.getCurrentPosition(success, error);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使用回调函数:
var xhr = new window.XMLHttpRequest(); xhr.open('GET', 'http://www.webdeveasy.com', true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log('Success'); } } }; xhr.send();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回调:async1(function() { async2(function() { async3(function() { async4(function() { .... .... .... asyncN(null, null); .... .... .... }, null); }, null); }, null); }, null);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回调:var counter = N; function success() { counter --; if (counter === 0) { alert('done!'); } } async1(success); async2(success); .... .... asyncN(success);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的实现方式(自备梯子),即$qService。如果你使用的其他的实现方式也不用担心,我在本文中提到的大部分方法都是通用的,如果不是,总有相同功能的方法。
基本用法
首先,让我们先创建一个deferred:var myFirstDeferred = $q.defer();1
1
再简单不过了,myFirstDeferred就是一个deferred,可以在异步操作结束后被resolve()或reject()。假设我们有个异步函数async(success, failure),参数为success回调和failure回调,当async函数执行完毕后,我们希望对myFirstDeferred进行resolve()或reject()操作:
async(function(value) { myFirstDeferred.resolve(value); }, function(errorReason) { myFirstDeferred.reject(errorReason); });1
2
3
4
5
1
2
3
4
5
由于AngularJS的$q Service不依赖于上下文的执行环境,上面的代码可以简写成:
async(myFirstDeferred.resolve, myFirstDeferred.reject);1
1
得到myFirstDeferred的promise实例,并对其分配成功回调和失败回调是非常简单的:
var myFirstPromise = myFirstDeferred.promise; myFirstPromise .then(function(data) { console.log('My first promise succeeded', data); }, function(error) { console.log('My first promise failed', error); });1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
请注意,即使我们的异步函数async()还没有被执行,只要我们获得了deferred实例并得到其对应的promise,我们就可以对promise分配回调函数了:
var anotherDeferred = $q.defer(); anotherDeferred.promise .then(function(data) { console.log('This success method was assigned BEFORE calling to async()', data); }, function(error) { console.log('This failure method was assigned BEFORE calling to async()', error); }); async(anotherDeferred.resolve, anotherDeferred.reject); anotherDeferred.promise .then(function(data) { console.log('This ANOTHER success method was assigned AFTER calling to async()', data); }, function(error) { console.log('This ANOTHER failure method was assigned AFTER calling to async()', error); });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的函数,这样调用者可以按需要分配成功或失败回调,而不能干涉或改变异步操作的状态:
function getData() { var deferred = $q.defer(); async(deferred.resolve, deferred.reject); return deferred.promise; } ... ... // Later, in a different file var dataPromise = getData() ... ... ... // Much later, at the bottom of that file :) dataPromise .then(function(data) { console.log('Success!', data); }, function(error) { console.log('Failure...', error); });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时还是分配了成功回调和失败回调,但其实也可以只分配成功回调或只分配失败回调:
promise.then(function() { console.log('Assign only success callback to promise'); }); promise.catch(function() { console.log('Assign only failure callback to promise'); // This is a shorthand for `promise.then(null, errorCallback)` });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():
promise.finally(function() { console.log('Assign a function that will be invoked both upon success and failure'); });1
2
3
1
2
3
上述代码其实和下面的代码是等价的:
var callback = function() { console.log('Assign a function that will be invoked both upon success and failure'); }; promise.then(callback, callback);1
2
3
4
1
2
3
4
值和promise的链式操作
设想我们有个异步函数async()返回一个promise,有下面一段有趣的代码:var promise1 = async(); var promise2 = promise1.then(function(x) { return x+1; });1
2
3
4
1
2
3
4
很容易理解,promise1.then()返回了另一个promise,这里命名为promise2,当promise1被处理(x作为参数传入),在promise1的成功回调中返回了x+1,这时promise2对应的处理函数将接收x+1作为参数。
再看一个类似的例子:
var promise2 = async().then(function(data) { console.log(data); ... // Do something with data // Returns nothing! });1
2
3
4
5
1
2
3
4
5
当async()函数返回的promise被处理,其成功回调函数没有返回任何值,那此时promise2对应的处理函数将接收到undefined。
上面可以看出,promise可以进行链式合成,并且上一个promise的处理结果将作为下一个promise的处理参数。
为了演示效果,下面使用一个很傻的使用promise的例子(没有必要使用promise):
// Let's imagine this is really an asynchronous function function async(value) { var deferred = $q.defer(); var asyncCalculation = value / 2; deferred.resolve(asyncCalculation); return deferred.promise; } var promise = async(8) .then(function(x) { return x+1; }) .then(function(x) { return x*2; }) .then(function(x) { return x-1; }); promise.then(function(x) { console.log(x); });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。来看下面的情形:
var promise = async1() .then(function(data) { // Assume async2() needs the response of async1() in order to work var async2Promise = async2(data); return async2Promise; });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,那上面的代码可以简写成:
var promise = async1() .then(async2);1
2
1
2
下面是另一个例子,同样也仅是用作演示:
// Let's imagine those are really asynchronous functions function async1(value) { var deferred = $q.defer(); var asyncCalculation = value * 2; deferred.resolve(asyncCalculation); return deferred.promise; } function async2(value) { var deferred = $q.defer(); var asyncCalculation = value + 1; deferred.resolve(asyncCalculation); return deferred.promise; } var promise = async1(10) .then(function(x) { return async2(x); }); promise.then(function(x) { console.log(x); });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。
上述代码可以用下面可读性更强的表达方式:
function logValue(value) { console.log(value); } async1(10) .then(async2) .then(logValue);1
2
3
4
5
6
7
1
2
3
4
5
6
7
这样很容易看出执行的流程。
上面这些关于promise链的例子的结果是我们乐观处理的结果,即:我们假设promise执行的是都是成功的回调函数,即deferred都被resolve()了。但是如果deferred被reject()了,那整个promise链都将被rejected:
// Let's imagine those are really asynchronous functions function async1(value) { var deferred = $q.defer(); var asyncCalculation = value * 2; deferred.resolve(asyncCalculation); return deferred.promise; } function async2(value) { var deferred = $q.defer(); deferred.reject('rejected for demonstration!'); return deferred.promise; } var promise = async1(10) .then(function(x) { return async2(x); }); promise.then( function(x) { console.log(x); }, function(reason) { console.log('Error: ' + reason); });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链更高级的表示方法:
async1() .then(async2) .then(async3) .catch(handleReject) .finally(freeResources);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:
var promise = async().then(function(value) { if (isSatisfied(value)) { return value; } else { return $q.reject('value is not satisfied'); } }, function(reason) { if (canRecovered(reason)) { return newPromiseOrValue; } else { return $q.reject(reason); } });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:
function getDataFromBackend(query) { var data = searchInCache(query); if (data) { return $q.when(data); } else { return makeAsyncBackendCall(query); } }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:
var jQueryPromise = $.ajax({ ... ... ... }); var angularPromise = $q.when(jQueryPromise);1
2
3
4
5
6
1
2
3
4
5
6
有时候我们需要执行多个异步函数,不在意其执行顺序,只想在它们都执行完成后得到通知,可以使用$q.all(promiseArr)帮助我们实现这个功能。假设我们有N个异步方法:async1(), …, asyncN(),都返回promise,下面的代码只有当所有的操作都被resolved时才能打印出”done”:
var allPromise = $q.all([ async1(), async2(), .... .... asyncN() ]); allPromise.then(function(values) { var value1 = values[0], value2 = values[1], .... .... valueN = values ; console.log('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 2 (教程篇)
- Part 17 Consuming ASP NET Web Service in AngularJS using $http
- Part 20 Create custom service in AngularJS
- Part 18 $http service in AngularJS
- 理解 AngularJS $q service and promises
- AngularJS ui-router 用resolve、service预先加载数据的正确写法
- [Whole Web] [AngularJS + Grunt] Using ng-html2js to Convert Templates into JavaScript
- Using AngularJS Promises
- 【JavaScript】前端开发框架三剑客—AngularJS VS. Backone.js VS.Ember.js
- How to call a service function in AngularJS ng-click
- AngularJS Front-End App with Cloud Storage Tutorial Part 1: Building a Minimal App in Seven Steps
- thrift js javascript C# Csharp webservice
- Javascript MVC/MVVM 框架对比, AngularJS vs Backbone vs Knockout
- AngularJS $http Service
- [AngularJS + Webpack] Uglifying your JavaScript
- 解析AngularJS service vs factory
- angularjs model.service vs provider vs factory?
- Code Organization in Large AngularJS and JavaScript Applications(waititng for translate)
- Dynamices CRM JS 类库 神器 XrmServiceToolkit - A Microsoft Dynamics CRM 2011 & CRM 2013 JavaScript Library