angularjs系列之轻松使用$q进行异步编程
第一部分关于js中的异步编程
异步编程简单的说就是你写了一段代码,但他不会按照你书写代码的顺序立即执行, 而是等到程序中发生了某个事件(如用户点击了某个按钮,某个ajax请求得到了响应)才去执行这段代码,而且这段代码可能执行一次(如一个ajax请求得 到了响应)、也可能执行很多次或者不执行(一个按钮被点击了许多次或者0次)这就是所谓的异步编程。
有两种异步程序模式单次执行模式和监听执行模式。 像ajax请求这样的就是属于单次执行模式,请求、回调只会进行一次。像事件绑定就属于监听执行模式,只要事件发生回调就可以不断的执行。但是不管是单次 执行模式还是监听执行模式在js程序中的共同点都是保存着一个函数引用(这个引用的表现形式就回调函数),在某个事件发生后开始执行这个函数引用中的代 码。
下面给出这两种模式的整体模型图:
异步执行体:包含异步执行代码(或者异步动作)和状态,状态不可逆。
回调执行体:包含回调执行代码和状态,状态不可逆。
两种执行体是依靠异步执行体发送信号通信。
监听执行模式和单次执行模式的区别:监听模式中会有多个异步执行体和回调执行体副本,且每个异步执行体副本关联一个回调执行体副本。多个副本的这两种执行体的执行代码是一样的,唯一区别的就是每个副本的状态不同。
代码举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//单次执行模式: funtion a(b) { console.log(“a process”); b(); }; function b() { console.log(“b process”); }; a(b); //发送信号 //监听执行模式(这里直接使用jquery): $(“ #button”).bind(“click”, function() { console.log(“click callback”); }); $(“ #button”).trigger(“click”); //发送信号 |
第二部分关于promise模式的异步编程
上面就是异步编程的两种形式,由于我们的$q是解决第一种模式中存在的问题的,所以这里就讨论讨论单次执行模式中存 在的问题。你可以设想这么一种场景,当你要在很多个ajax请求响应完成后做一件事情,你用现有的js的回调方式,会不会发现回调的层次很深、代码十分混 乱。而然我们promise模式的异步编程方式就能很好的管理这些单次执行模式下的代码,使你的代码书写起来更加优雅,说白了promise不产生新的东西只是一个语法糖使你编写出更加优雅的异步程序代码。
那promise模式到底是什么样的呢,说白了就是把把我上面的单次执行模式图抽象化了,用一个defer对象(延 期对象)代表异步执行体,用一个promise对象(承诺对象)代表回调执行体,这个defer对象可以发送消息,promise接受消息,然后执行相应 的回调函数。Promise就是异步执行体和回调执行体之间的桥梁,这样的好处是异步执行体和回调执行体这种模型很好的对异步动作和回调动作进行了解耦。 你可以在promise上面好好的操纵你的回调执行体,而只是接受一个来自defer发送的参数。
这里还不能很好的体现出promise模式的优势,而她真正的优势是在这种模型下扩充的api使其发挥了真正的强大 作用。比如说promise对象的then方法,这个方法就是支持异步链式编程最重要的方法,他也是使代码更加优雅最重要的方法。还有比如说all接收一 个promise数组返回一个新的promise,当前面的所有promise状态都完成之后这个新的promise才能完成,这个很适合多个ajax后 处理某些事情。不过可能你还不能明白,下面在介绍$qAPI的时候有详细的介绍。
第三部分 $q服务的API详解
下面我们通过讲解$q的API让你更多的了解promise异步编程模式。$q是做为angularjs的一个服务而存在的,只是对promise异步编程模式的一个简化实现版,源码中剔除注释实现代码也就二百多行,下面开始介绍$q的API。
defer对象(延迟对象)可以通$q.defer()获取,下面是defer对象的api:
方法:
resolve(value):向promise对象异步执行体发送消息告诉他我已经成功完成任务,value即为发送的消息。
reject(value): 向promise对象异步执行体发送消息告诉他我已经不可能完成这个任务了,value即为发送的消息。
notify(value): 向promise对象异步执行体发送消息告诉他我现在任务完成的情况,value即为发送的消息。
这些消息发送完promise会调用现有的回调函数。
属性:
promise即与这个defer对象的承诺对象。
从上可以看出defer主要是用来发送消息的。
promise对象可以通过defer.promise获取,下面是promise对象的api:
方法:
then(successCallback,errorCallback,notifyCallback):参 数为不同消息下的不同回调函数,defer发送不同的消息执行不同的回调函数,消息作为这些回调函数的参数传递。返回值为回一个promise对象为支持 链式调用而存在。当第一个defer对象发送消息后,后面的promise对应的defer对象也会发送消息,但是发送的消息不一样,不管第一个 defer对象发送的是reject还是resolve,第二个及其以后的都是发送的resolve,消息是可传递的。
catch(errorCallback):then(null,errorCallback)的缩写。
finally(callback):相当于then(callback,callback)的缩写,这个finally中的方法不接受参数,却可以将defer发送的消息和消息类型成功传递到下一个then中。
代码举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
function f1(num) { document.write( "success:" + (num++) + "<br/>" ); return num; } function f2(num) { document.write( "errror:" + (num++) + "<br/>" ); return num; } var defer = $q.defer(); var promise = defer.promise; //方式1 // promise.then(f1,f2).then(f1,f2); // 方式2 // promise.then(f1,f2); // promise.then(f1,f2); // 方式3 // promise.then(f1,f2).then(f1,f2); // promise.catch(f2); // promise.finally(f2); //方式4 // promise.finally(f2).then(f1,f2); defer.reject(1); 方式1的结果: errror: 1 success: 2 方式2的结果: errror: 1 errror: 1 方式3的结果: errror: 1 errror: 1 error: NaN success: 2 方式4的结果: Error: NaN Error: 1 |
现在继续$q的api:
方法:
defer():用来生成一个延迟对象 var defer =$q.defer();
reject():参数接收错误消息,相当于在回调函数中抛出一个异常,然后在下一个then中调用错误的回调函数。
代码举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var defer = $q.defer(); var promise = defer.promise; promise.then( function () { return $q.reject( "success error" ); }, function () { return $q.reject( "error error" ); }).then( function (info) { document.write( "s:" + info + "<br/>" ); }, function (info) { document.write( "e:" + info + "<br/>" ); }); //方式1 // defer.reject(1); //方式2 // defer.resolve(1); 方式1运行结果 e: error error 方式2运行结果 e: success error |
all():参数接收为一个promise数组,返回一个新的单一promise对象,当这些promise对象对应defer对象全部解决这个单一promise对象才会解决,当这些promise对象中有一个被reject了,这个单一promise同样的被reject了。
代码举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
var defer1 = $q.defer(); var promise1 = defer1.promise; promise1.then( function (num) { console.log( "success" + num); }, function (num) { console.log( "error" + num); }); var defer2 = $q.defer(); var promise2 = defer2.promise; promise1.then( function (num) { console.log( "success" + num); }, function (num) { console.log( "error" + num); }); var promise3 = $q.all([promise1, promise1]); promise3.then( function (num) { console.log( "s:" + num); }, function (num) { console.log( "e:" + num); }); //方式1 // defer1.resolve(1); // defer2.resolve(1); //方式2 //defer1.reject(1); 方式1的结果: success1 success2: 1 s: 1, 1 方式2的结果: error1 e: 1 |
when():接收第一个参数为一个任意值或者是一个promise对象,其他 3个同promise的then方法,返回值为一个promise对象。第一个参数若不是promise对象则直接运行success回调且消息为这个对 象,若为promise那么返回的promise其实就是对这个promise类型的参数的一个包装而已,被传入的这个promise对应的defer发 送的消息,会被我们when函数返回的promise对象所接收到。
代码举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var promise = $q.when(1, function (num) { console.log( "s" + num); }, function () { console.log( "e" ); }); var defer1 = $q.defer(); var promise1 = defer1.promise; var promise2 = $q.when(promise1, function (num) { console.log( "s" + num); }, function () { console.log( "e" ); }); defer1.reject(1); 运行结果: s1 e |
对上面还有一个注意事项就是defer对象发送消息不会立即执行的,而是把要执行的代码放到了rootScope的evalAsync队列当中,当时scope.$apply的时候才会被promise接收到这个消息。
到这里$q服务全部介绍完了,对于angular中的then的链式调用中如果defer发送的reject的那么 只有第一个promise是reject的回调,其他的都是resolve的回调这里多少觉得是不合适的,不知道是个bug还是就是这样设计?$q只适合 单次执行模式,不知道是否适合扩展成监听执行模式?这都是大家值得思考的问题。