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 **/