Promise

Promises模型

Promises是一个异步编程模型,它通过一组API来规范化异步操作。

Promises模型的基本概念可以总结为:

  1. Promises 作为结果或错误的占位符
  2. 提供了一种在结果完成或错误发生时的通知方式

对于Promises模型而言,已经提出了多个实现的草案:如Promises/A,Promises/B等等。还有一些可以参看:http://wiki.commonjs.org/wiki/Promises

Promises/A

简称“thenable",事实上,每一个Promise对象都有一个方法:then(fulfilledHandler,errorHandler,progressHandler)。因为Promise对象有三个状态:unfulfilled,fulfilled,failed,也就是未完成的,完成的和失败的。当一个Promise对象由unfulfilled状态变为fulfilled状态后,fulfilledHandler函数会被调用。当进度事件发生时,progressHandler会被调用。通常progress事件可以省略。

jQuery Deferred对象

Deferred概念从jQuery的1.5版本开始引用,它是Promises/A标准的一种衍生实现。通过调用jQuery.Deferred()方法,可以创建Deferred对象,它是一个可以链式操作的对象,并能够注册多个回调函数到回调队列,在异步操作成功或者失败时转达消息。

defer在英文中的意思是延迟,因此我们可以理解为Deferred对象为延迟执行的对象。

Deferred对象有三个动作:resolve(解决)、reject(拒绝)和notify(通知)

Deferred对象可以通过一些方法主动调用这三个动作:deferred.resolve()、deferred.reject()和deferred.notify()。当然,还有一些回调对应不同的状态,如:

  • resolved时调用deferred.done()回调;
  • rejected时调用deferred.fail()回调;
  • resolved或者rejected时调用deferred.always()回调;
  • inProgress时调用deferred.progress()回调;
  • resolved、rejected或者inProgress时调用then()回调;

有人可能会问then()和always()有什么区别?其实这两个是相似的,只是then函数有两个参数:resolved时的回调和rejected时的回调,相当于将done()和fail()函数合并了,而always函数只有一个回调函数作为参数,不管是resolved还是rejected时,都会执行这个回调。

 1 jQuery.Deferred = function( func ) {
 2     /* Callback参数
 3     once: 所有回调函数只执行一次
 4     memory: 触发回调后对象可以继续添加函数,添加的函数会马上被触发
 5     unique: 保证相同的回调函数只能添加一次
 6     stopOnFalse: 当回调返回false时停止触发
 7     */
 8         var tuples = [
 9                 // action, add listener, listener list, final state
10                 [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
11                 [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
12                 [ "notify", "progress", jQuery.Callbacks("memory") ]
13             ],
14             state = "pending",
15             promise = {
16                 state: function() {
17                     return state;
18                 },
19                 always: function() {
20                     deferred.done( arguments ).fail( arguments );
21                     return this;
22                 },
23                 then: function( /* fnDone, fnFail, fnProgress */ ) {
24                     var fns = arguments;
25                     return jQuery.Deferred(function( newDefer ) {
26                         jQuery.each( tuples, function( i, tuple ) {
27                             var action = tuple[ 0 ],
28                                 fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
29                             // deferred[ done | fail | progress ] for forwarding actions to newDefer
30                             deferred[ tuple[1] ](function() {
31                                 var returned = fn && fn.apply( this, arguments );
32                                 if ( returned && jQuery.isFunction( returned.promise ) ) {
33                                     returned.promise()
34                                         .done( newDefer.resolve )
35                                         .fail( newDefer.reject )
36                                         .progress( newDefer.notify );
37                                 } else {
38                                     newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
39                                 }
40                             });
41                         });
42                         fns = null;
43                     }).promise();
44                 },
45                 // Get a promise for this deferred
46                 // If obj is provided, the promise aspect is added to the object
47                 promise: function( obj ) {
48                     return obj != null ? jQuery.extend( obj, promise ) : promise;
49                 }
50             },
51             deferred = {};
52 
53         // Keep pipe for back-compat
54         promise.pipe = promise.then;
55 
56         // Add list-specific methods
57         jQuery.each( tuples, function( i, tuple ) {
58             var list = tuple[ 2 ],
59                 stateString = tuple[ 3 ];
60 
61             // promise[ done | fail | progress ] = list.add
62             promise[ tuple[1] ] = list.add;
63 
64             // Handle state
65             if ( stateString ) {
66                 list.add(function() {
67                     // state = [ resolved | rejected ]
68                     state = stateString;
69 
70                 // [ reject_list | resolve_list ].disable; progress_list.lock
71                 }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
72             }
73 
74             // deferred[ resolve | reject | notify ]
75             deferred[ tuple[0] ] = function() {
76                 deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
77                 return this;
78             };
79             deferred[ tuple[0] + "With" ] = list.fireWith;
80         });
81 
82         // Make the deferred a promise
83         promise.promise( deferred );
84 
85         // Call given func if any
86         if ( func ) {
87             func.call( deferred, deferred );
88         }
89 
90         // All done!
91         return deferred;
92     }
jQuery Deferred

此外,还有两个方法:

  • deferred.promise():在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。
  • deferred.when(): 可以传入多个deferred对象,并为他们指定同一个回调(其实就是deferred队列管理的功能)。

关于Deferred对象的更多细节请看API: http://api.jquery.com/category/deferred-object/

Node.js和Promises

我们知道,node.js是异步编程的,对于新手而言,异步编程导致的一个常见的问题就是异步嵌套过深。解决的方法有若干个,一个是使用Event,也就是在合适的时间emit某一个事件,那么代码就变为了一行一行的on('xxx',callback);另一个解决方法就是使用异步管理的库,如async等;还有就是我们现在讨论的Promises模型。

Nodejs中比较流行的Promises库就是Q Library。它的项目主页是:https://github.com/kriskowal/q

  • Q.fcall():创建Promise对象
  • Q.defer():生成defer对象,可以通过调用resolve和reject方法等改变对象的状态
  • Q.all():等待一个数组中的所有Promise对象都执行完毕

一个例子是:

function get_all_the_things(things) {
    var the_promises = [];

    things.forEach(function(thing) {
        var deferred = Q.defer();
        get_a_thing(thing, function(result) {
            deferred.resolve(result);
        });
        the_promises.push(deferred.promise);
    });

    return Q.all(the_promises);
}

 

还有其他的功能,由于还不熟悉,就先到这里。

posted @ 2013-12-26 11:15  cubika  阅读(537)  评论(0编辑  收藏  举报