Class.Thenable mixin.
[mootools.git] / Source / Class / Class.Thenable.js
blobb000eabb21bd9e9ed9a12db50c046263a0b583ea
1 /*
2 ---
4 name: Class.Thenable
6 description: Contains a Utility Class that can be implemented into your own Classes to make them "thenable".
8 license: MIT-style license.
10 requires: Class
12 provides: [Class.Thenable]
14 ...
17 (function(){
19 var STATE_PENDING = 0,
20         STATE_FULFILLED = 1,
21         STATE_REJECTED = 2;
23 var Thenable = Class.Thenable = new Class({
25         $thenableState: STATE_PENDING,
26         $thenableResult: null,
27         $thenableReactions: [],
29         resolve: function(value){
30                 resolve(this, value);
31                 return this;
32         },
34         reject: function(reason){
35                 reject(this, reason);
36                 return this;
37         },
39         getThenableState: function(){
40                 switch (this.$thenableState){
41                         case STATE_PENDING:
42                                 return 'pending';
44                         case STATE_FULFILLED:
45                                 return 'fulfilled';
47                         case STATE_REJECTED:
48                                 return 'rejected';
49                 }
50         },
52         resetThenable: function(reason){
53                 reject(this, reason);
54                 reset(this);
55                 return this;
56         },
58         then: function(onFulfilled, onRejected){
59                 if (typeof onFulfilled !== 'function') onFulfilled = 'Identity';
60                 if (typeof onRejected !== 'function') onRejected = 'Thrower';
62                 var thenable = new Thenable();
64                 this.$thenableReactions.push({
65                         thenable: thenable,
66                         fulfillHandler: onFulfilled,
67                         rejectHandler: onRejected
68                 });
70                 if (this.$thenableState !== STATE_PENDING){
71                         react(this);
72                 }
74                 return thenable;
75         },
77         'catch': function(onRejected){
78                 return this.then(null, onRejected);
79         }
81 });
83 Thenable.extend({
84         resolve: function(value){
85                 var thenable;
86                 if (value instanceof Thenable){
87                         thenable = value;
88                 } else {
89                         thenable = new Thenable();
90                         resolve(thenable, value);
91                 }
92                 return thenable;
93         },
94         reject: function(reason){
95                 var thenable = new Thenable();
96                 reject(thenable, reason);
97                 return thenable;
98         }
99 });
101 // Private functions
103 function resolve(thenable, value){
104         if (thenable.$thenableState === STATE_PENDING){
105                 if (thenable === value){
106                         reject(thenable, new TypeError('Tried to resolve a thenable with itself.'));
107                 } else if (value && (typeof value === 'object' || typeof value === 'function')){
108                         try {
109                                 var then = value.then;
110                         } catch (exception){
111                                 reject(thenable, exception);
112                         }
113                         if (typeof then === 'function'){
114                                 var resolved = false;
115                                 defer(function(){
116                                         try {
117                                                 then.call(
118                                                         value,
119                                                         function(nextValue){
120                                                                 if (!resolved){
121                                                                         resolved = true;
122                                                                         resolve(thenable, nextValue);
123                                                                 }
124                                                         },
125                                                         function(reason){
126                                                                 if (!resolved){
127                                                                         resolved = true;
128                                                                         reject(thenable, reason);
129                                                                 }
130                                                         }
131                                                 );
132                                         } catch (exception) {
133                                                 if (!resolved){
134                                                         resolved = true;
135                                                         reject(thenable, exception);
136                                                 }
137                                         }
138                                 });
139                         } else {
140                                 fulfill(thenable, value);
141                         }
142                 } else {
143                         fulfill(thenable, value);
144                 }
145         }
148 function fulfill(thenable, value){
149         if (thenable.$thenableState === STATE_PENDING){
150                 thenable.$thenableResult = value;
151                 thenable.$thenableState = STATE_FULFILLED;
153                 react(thenable);
154         }
157 function reject(thenable, reason){
158         if (thenable.$thenableState === STATE_PENDING){
159                 thenable.$thenableResult = reason;
160                 thenable.$thenableState = STATE_REJECTED;
162                 react(thenable);
163         }
166 function reset(thenable){
167         if (thenable.$thenableState !== STATE_PENDING){
168                 thenable.$thenableResult = null;
169                 thenable.$thenableState = STATE_PENDING;
170         }
173 function react(thenable){
174         var state = thenable.$thenableState,
175                 result = thenable.$thenableResult,
176                 reactions = thenable.$thenableReactions,
177                 type;
179         if (state === STATE_FULFILLED){
180                 thenable.$thenableReactions = [];
181                 type = 'fulfillHandler';
182         } else if (state == STATE_REJECTED){
183                 thenable.$thenableReactions = [];
184                 type = 'rejectHandler';
185         }
187         if (type){
188                 defer(handle.pass([result, reactions, type]));
189         }
192 function handle(result, reactions, type){
193         for (var i = 0, l = reactions.length; i < l; ++i){
194                 var reaction = reactions[i],
195                         handler = reaction[type];
197                 if (handler === 'Identity'){
198                         resolve(reaction.thenable, result);
199                 } else if (handler === 'Thrower'){
200                         reject(reaction.thenable, result);
201                 } else {
202                         try {
203                                 resolve(reaction.thenable, handler(result));
204                         } catch (exception) {
205                                 reject(reaction.thenable, exception);
206                         }
207                 }
208         }
211 var defer;
212 if (typeof process !== 'undefined' && typeof process.nextTick === 'function'){
213         defer = process.nextTick;
214 } else if (typeof setImmediate !== 'undefined'){
215         defer = setImmediate;
216 } else {
217         defer = function(fn){
218                 setTimeout(fn, 0);
219         };
222 })();