1 /** 2 * Emily.js - http://flams.github.com/emily/ 3 * Copyright(c) 2012-2014 Olivier Scherrer <pode.fr@gmail.com> 4 * MIT Licensed 5 */ 6 "use strict"; 7 8 var Observable = require("./Observable"), 9 StateMachine = require("./StateMachine"); 10 11 /** 12 * @class 13 * Create a promise/A+ 14 */ 15 module.exports = function PromiseConstructor() { 16 17 /** 18 * The fulfilled value 19 * @private 20 */ 21 var _value = null, 22 23 /** 24 * The rejection reason 25 * @private 26 */ 27 _reason = null, 28 29 /** 30 * The funky observable 31 * @private 32 */ 33 _observable = new Observable(), 34 35 /** 36 * The stateMachine 37 * @private 38 */ 39 _stateMachine = new StateMachine("Pending", { 40 41 // The promise is pending 42 "Pending": [ 43 44 // It can only be fulfilled when pending 45 ["fulfill", function onFulfill(value) { 46 _value = value; 47 _observable.notify("fulfill", value); 48 // Then it transits to the fulfilled state 49 }, "Fulfilled"], 50 51 // it can only be rejected when pending 52 ["reject", function onReject(reason) { 53 _reason = reason; 54 _observable.notify("reject", reason); 55 // Then it transits to the rejected state 56 }, "Rejected"], 57 58 // When pending, add the resolver to an observable 59 ["toFulfill", function toFulfill(resolver) { 60 _observable.watch("fulfill", resolver); 61 }], 62 63 // When pending, add the resolver to an observable 64 ["toReject", function toReject(resolver) { 65 _observable.watch("reject", resolver); 66 }]], 67 68 // When fulfilled, 69 "Fulfilled": [ 70 // We directly call the resolver with the value 71 ["toFulfill", function toFulfill(resolver) { 72 resolver(_value); 73 }]], 74 75 // When rejected 76 "Rejected": [ 77 // We directly call the resolver with the reason 78 ["toReject", function toReject(resolver) { 79 resolver(_reason); 80 }]] 81 }); 82 83 /** 84 * Fulfilled the promise. 85 * A promise can be fulfilld only once. 86 * @param the fulfillment value 87 * @returns the promise 88 */ 89 this.fulfill = function fulfill(value) { 90 setTimeout(function () { 91 _stateMachine.event("fulfill", value); 92 }, 0); 93 return this; 94 95 }; 96 97 /** 98 * Reject the promise. 99 * A promise can be rejected only once. 100 * @param the rejection value 101 * @returns true if the rejection function was called 102 */ 103 this.reject = function reject(reason) { 104 setTimeout(function () { 105 _stateMachine.event("reject", reason); 106 }, 0); 107 return this; 108 }; 109 110 /** 111 * The callbacks to call after fulfillment or rejection 112 * @param {Function} fulfillmentCallback the first parameter is a success function, it can be followed by a scope 113 * @param {Function} the second, or third parameter is the rejection callback, it can also be followed by a scope 114 * @examples: 115 * 116 * then(fulfillment) 117 * then(fulfillment, scope, rejection, scope) 118 * then(fulfillment, rejection) 119 * then(fulfillment, rejection, scope) 120 * then(null, rejection, scope) 121 * @returns {Promise} the new promise 122 */ 123 this.then = function then() { 124 var promise = new PromiseConstructor(); 125 126 // If a fulfillment callback is given 127 if (arguments[0] instanceof Function) { 128 // If the second argument is also a function, then no scope is given 129 if (arguments[1] instanceof Function) { 130 _stateMachine.event("toFulfill", this.makeResolver(promise, arguments[0])); 131 } else { 132 // If the second argument is not a function, it's the scope 133 _stateMachine.event("toFulfill", this.makeResolver(promise, arguments[0], arguments[1])); 134 } 135 } else { 136 // If no fulfillment callback given, give a default one 137 _stateMachine.event("toFulfill", this.makeResolver(promise, function () { 138 promise.fulfill(_value); 139 })); 140 } 141 142 // if the second arguments is a callback, it's the rejection one, and the next argument is the scope 143 if (arguments[1] instanceof Function) { 144 _stateMachine.event("toReject", this.makeResolver(promise, arguments[1], arguments[2])); 145 } 146 147 // if the third arguments is a callback, it's the rejection one, and the next arguments is the sopce 148 if (arguments[2] instanceof Function) { 149 _stateMachine.event("toReject", this.makeResolver(promise, arguments[2], arguments[3])); 150 } 151 152 // If no rejection callback is given, give a default one 153 if (!(arguments[1] instanceof Function) && 154 !(arguments[2] instanceof Function)) { 155 _stateMachine.event("toReject", this.makeResolver(promise, function () { 156 promise.reject(_reason); 157 })); 158 } 159 160 return promise; 161 }; 162 163 /** 164 * Cast a thenable into an Emily promise 165 * @returns {Boolean} false if the given promise is not a thenable 166 */ 167 this.cast = function cast(thenable) { 168 if (thenable instanceof PromiseConstructor || 169 typeof thenable == "object" || 170 typeof thenable == "function") { 171 172 thenable.then(this.fulfill.bind(this), 173 this.reject.bind(this)); 174 175 return true; 176 } else { 177 return false; 178 } 179 }; 180 181 /** 182 * Make a resolver 183 * for debugging only 184 * @private 185 * @returns {Function} a closure 186 */ 187 this.makeResolver = function makeResolver(promise, func, scope) { 188 return function resolver(value) { 189 var returnedPromise; 190 191 try { 192 returnedPromise = func.call(scope, value); 193 if (returnedPromise === promise) { 194 throw new TypeError("Promise A+ 2.3.1: If `promise` and `x` refer to the same object, reject `promise` with a `TypeError' as the reason."); 195 } 196 if (!promise.cast(returnedPromise)) { 197 promise.fulfill(returnedPromise); 198 } 199 } catch (err) { 200 promise.reject(err); 201 } 202 203 }; 204 }; 205 206 /** 207 * Returns the reason 208 * for debugging only 209 * @private 210 */ 211 this.getReason = function getReason() { 212 return _reason; 213 }; 214 215 /** 216 * Returns the reason 217 * for debugging only 218 * @private 219 */ 220 this.getValue = function getValue() { 221 return _value; 222 }; 223 224 /** 225 * Get the promise's observable 226 * for debugging only 227 * @private 228 * @returns {Observable} 229 */ 230 this.getObservable = function getObservable() { 231 return _observable; 232 }; 233 234 /** 235 * Get the promise's stateMachine 236 * for debugging only 237 * @private 238 * @returns {StateMachine} 239 */ 240 this.getStateMachine = function getStateMachine() { 241 return _stateMachine; 242 }; 243 244 /** 245 * Get the statesMachine's states 246 * for debugging only 247 * @private 248 * @returns {Object} 249 */ 250 this.getStates = function getStates() { 251 return _states; 252 }; 253 };