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 };