1 /**
  2  * @fileOverview implement Promise specification by KISSY
  3  * @author yiminghe@gmail.com
  4  */
  5 (function (S, undefined) {
  6 
  7     /**
  8      * two effects:
  9      * 1. call fulfilled with immediate value
 10      * 2. push fulfilled in right promise
 11      * @private
 12      * @param fulfilled
 13      * @param rejected
 14      */
 15     function promiseWhen(promise, fulfilled, rejected) {
 16         // simply call rejected
 17         if (promise instanceof Reject) {
 18             // if there is a rejected , should always has! see when()
 19             if (!rejected) {
 20                 S.error("no rejected callback!");
 21             }
 22             return rejected(promise.__promise_value);
 23         }
 24 
 25         var v = promise.__promise_value,
 26             pendings = promise.__promise_pendings;
 27 
 28         // unresolved
 29         // pushed to pending list
 30         if (pendings) {
 31             pendings.push([fulfilled, rejected]);
 32         }
 33         // rejected or nested promise
 34         else if (isPromise(v)) {
 35             promiseWhen(v, fulfilled, rejected);
 36         } else {
 37             // fulfilled value
 38             // normal value represents ok
 39             // need return user's return value
 40             // if return promise then forward
 41             return fulfilled && fulfilled(v);
 42         }
 43         return undefined;
 44     }
 45 
 46     /**
 47      * @class Defer constructor For KISSY,implement Promise specification.
 48      * @memberOf KISSY
 49      * @name Defer
 50      */
 51     function Defer(promise) {
 52         var self = this;
 53         if (!(self instanceof Defer)) {
 54             return new Defer(promise);
 55         }
 56         // http://en.wikipedia.org/wiki/Object-capability_model
 57         // principal of least authority
 58         /**
 59          * @description defer object's promise
 60          * @type KISSY.Promise
 61          * @memberOf KISSY.Defer#
 62          * @name promise
 63          */
 64         self.promise = promise || new Promise();
 65     }
 66 
 67     Defer.prototype =
 68     /**
 69      * @lends KISSY.Defer.prototype
 70      */
 71     {
 72         constructor:Defer,
 73         /**
 74          * fulfill defer object's promise
 75          * note: can only be called once
 76          * @param value defer object's value
 77          * @returns defer object's promise
 78          */
 79         resolve:function (value) {
 80             var promise = this.promise,
 81                 pendings;
 82             if (!(pendings = promise.__promise_pendings)) {
 83                 return undefined;
 84             }
 85             // set current promise 's resolved value
 86             // maybe a promise or instant value
 87             promise.__promise_value = value;
 88             pendings = [].concat(pendings);
 89             promise.__promise_pendings = undefined;
 90             S.each(pendings, function (p) {
 91                 promiseWhen(promise, p[0], p[1]);
 92             });
 93             return value;
 94         },
 95         /**
 96          * reject defer object's promise
 97          * @param reason
 98          * @returns defer object's promise
 99          */
100         reject:function (reason) {
101             return this.resolve(new Reject(reason));
102         }
103     };
104 
105     function isPromise(obj) {
106         return  obj && obj instanceof Promise;
107     }
108 
109     /**
110      * @class
111      * Promise constructor.
112      * This class should not be instantiated manually.
113      * Instances will be created and returned as needed by {@link KISSY.Defer#promise}
114      * @param [v] promise 's resolved value
115      * @memberOf KISSY
116      * @name Promise
117      */
118     function Promise(v) {
119         var self = this;
120         // maybe internal value is also a promise
121         self.__promise_value = v;
122         if (v === undefined) {
123             // unresolved
124             self.__promise_pendings = [];
125         }
126     }
127 
128     Promise.prototype =
129     /**
130      * @lends KISSY.Promise.prototype
131      */
132     {
133         constructor:Promise,
134         /**
135          * register callbacks when this promise object is resolved
136          * @param {Function(*)} fulfilled called when resolved successfully,pass a resolved value to this function and
137          *                      return a value (could be promise object) for the new promise's resolved value.
138          * @param {Function(*)} [rejected] called when error occurs,pass error reason to this function and
139          *                      return a new reason for the new promise's error reason
140          * @returns {KISSY.Promise} a new promise object
141          */
142         then:function (fulfilled, rejected) {
143             return when(this, fulfilled, rejected);
144         },
145         /**
146          * call rejected callback when this promise object is rejected
147          * @param {Function(*)} rejected called with rejected reason
148          * @returns {KISSY.Promise} a new promise object
149          */
150         fail:function (rejected) {
151             return when(this, 0, rejected);
152         },
153         /**
154          * call callback when this promise object is rejected or resolved
155          * @param {Function} callback the second parameter is
156          * true when resolved and false when rejected
157          * @@returns {KISSY.Promise} a new promise object
158          */
159         fin:function (callback) {
160             return when(this, function (value) {
161                 return callback(value, true);
162             }, function (reason) {
163                 return callback(reason, false);
164             });
165         },
166         /**
167          * whether the given object is a resolved promise
168          * if it is resolved with another promise,
169          * then that promise needs to be resolved as well.
170          */
171         isResolved:function () {
172             return isResolved(this);
173         },
174         /**
175          * whether the given object is a rejected promise
176          */
177         isRejected:function () {
178             return isRejected(this);
179         }
180     };
181 
182     function Reject(reason) {
183         if (reason instanceof Reject) {
184             return reason;
185         }
186         var self = this;
187         Promise.apply(self, arguments);
188         if (self.__promise_value instanceof Promise) {
189             S.error('assert.not(this.__promise_value instanceof promise) in Reject constructor');
190         }
191         return undefined;
192     }
193 
194     S.extend(Reject, Promise);
195 
196     /**
197      * wrap for promiseWhen
198      * @param value
199      * @param fulfilled
200      * @param [rejected]
201      */
202     function when(value, fulfilled, rejected) {
203         var defer = new Defer(),
204             done = 0;
205 
206         // wrap user's callback to catch exception
207         function _fulfilled(value) {
208             try {
209                 return fulfilled ? fulfilled(value) :
210                     // propagate
211                     value;
212             } catch (e) {
213                 // print stack info for firefox/chrome
214                 S.log(e.stack || e, "error");
215                 return new Reject(e);
216             }
217         }
218 
219         function _rejected(reason) {
220             try {
221                 return rejected ?
222                     // error recovery
223                     rejected(reason) :
224                     // propagate
225                     new Reject(reason);
226             } catch (e) {
227                 // print stack info for firefox/chrome
228                 S.log(e.stack || e, "error");
229                 return new Reject(e);
230             }
231         }
232 
233         function finalFulfill(value) {
234             if (done) {
235                 S.error("already done at fulfilled");
236                 return;
237             }
238             if (value instanceof Promise) {
239                 S.error("assert.not(value instanceof Promise) in when")
240             }
241             done = 1;
242             defer.resolve(_fulfilled(value));
243         }
244 
245         if (value instanceof  Promise) {
246             promiseWhen(value, finalFulfill, function (reason) {
247                 if (done) {
248                     S.error("already done at rejected");
249                     return;
250                 }
251                 done = 1;
252                 // _reject may return non-Reject object for error recovery
253                 defer.resolve(_rejected(reason));
254             });
255         } else {
256             finalFulfill(value);
257         }
258 
259         // chained and leveled
260         // wait for value's resolve
261         return defer.promise;
262     }
263 
264     function isResolved(obj) {
265         // exclude Reject at first
266         return !isRejected(obj) &&
267             isPromise(obj) &&
268             // self is resolved
269             (obj.__promise_pendings === undefined) &&
270             // value is a resolved promise or value is immediate value
271             (
272                 // immediate value
273                 !isPromise(obj.__promise_value) ||
274                     // resolved with a resolved promise !!! :)
275                     // Reject.__promise_value is string
276                     isResolved(obj.__promise_value)
277                 );
278     }
279 
280     function isRejected(obj) {
281         return isPromise(obj) &&
282             (obj.__promise_pendings === undefined) &&
283             (obj.__promise_value instanceof Reject);
284     }
285 
286     KISSY.Defer = Defer;
287     KISSY.Promise = Promise;
288 
289     S.mix(Promise,
290         /**
291          * @lends KISSY.Promise
292          */
293         {
294             /**
295              * register callbacks when obj as a promise is resolved
296              * or call fulfilled callback directly when obj is not a promise object
297              * @param {KISSY.Promise|*} obj a promise object or value of any type
298              * @param {Function(*)} fulfilled called when obj resolved successfully,pass a resolved value to this function and
299              *                      return a value (could be promise object) for the new promise's resolved value.
300              * @param {Function(*)} [rejected] called when error occurs in obj,pass error reason to this function and
301              *                      return a new reason for the new promise's error reason
302              * @returns {KISSY.Promise} a new promise object
303              * @example
304              * <code>
305              * function check(p){
306              *   S.Promise.when(p,function(v){
307              *     alert(v===1);
308              *   });
309              * }
310              *
311              * var defer=S.Defer();
312              * defer.resolve(1);
313              *
314              * check(1); // => alert(true)
315              *
316              * check(defer.promise); //=> alert(true);
317              * </code>
318              * @function
319              */
320             when:when,
321             /**
322              * whether the given object is a promise
323              * @function
324              * @param obj the tested object
325              */
326             isPromise:isPromise,
327             /**
328              * whether the given object is a resolved promise
329              * @function
330              * @param obj the tested object
331              */
332             isResolved:isResolved,
333             /**
334              * whether the given object is a rejected promise
335              * @function
336              * @param obj the tested object
337              */
338             isRejected:isRejected,
339             /**
340              * return a new promise
341              * which is resolved when all promises is resolved
342              * and rejected when any one of promises is rejected
343              * @param {KISSY.Promise[]} promises list of promises
344              */
345             all:function (promises) {
346                 var count = promises.length;
347                 if (!count) {
348                     return promises;
349                 }
350                 var defer = Defer();
351                 for (var i = 0; i < promises.length; i++) {
352                     (function (promise, i) {
353                         when(promise, function (value) {
354                             promises[i] = value;
355                             if (--count === 0) {
356                                 // if all is resolved
357                                 // then resolve final returned promise with all value
358                                 defer.resolve(promises);
359                             }
360                         }, function (r) {
361                             // if any one is rejected
362                             // then reject final return promise with first reason
363                             defer.reject(r);
364                         });
365                     })(promises[i], i);
366                 }
367                 return defer.promise;
368             }
369         });
370 
371 })(KISSY);
372 
373 /**
374  * refer:
375  *  - http://wiki.commonjs.org/wiki/Promises
376  *  - http://en.wikipedia.org/wiki/Futures_and_promises#Read-only_views
377  *  - http://en.wikipedia.org/wiki/Object-capability_model
378  *  - https://github.com/kriskowal/q
379  *  - http://www.sitepen.com/blog/2010/05/03/robust-promises-with-dojo-deferred-1-5/
380  *  - http://dojotoolkit.org/documentation/tutorials/1.6/deferreds/
381  **/