2 var util = require('util');
3 var EventEmitter = require('events').EventEmitter;
4 function toArray(arr, start, end) {
5 return Array.prototype.slice.call(arr, start, end)
7 function strongUnshift(x, arrLike) {
8 var arr = toArray(arrLike);
15 * MPromise constructor.
17 * _NOTE: The success and failure event names can be overridden by setting `Promise.SUCCESS` and `Promise.FAILURE` respectively._
19 * @param {Function} back a function that accepts `fn(err, ...){}` as signature
20 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
21 * @event `reject`: Emits when the promise is rejected (event name may be overridden)
22 * @event `fulfill`: Emits when the promise is fulfilled (event name may be overridden)
25 function Promise(back) {
26 this.emitter = new EventEmitter();
29 if ('function' == typeof back) {
39 module.exports = Promise;
45 Promise.SUCCESS = 'fulfill';
46 Promise.FAILURE = 'reject';
50 * Adds `listener` to the `event`.
52 * If `event` is either the success or failure event and the event has already been emitted, the`listener` is called immediately and passed the results of the original emitted event.
54 * @param {String} event
55 * @param {Function} callback
56 * @return {MPromise} this
59 Promise.prototype.on = function (event, callback) {
60 if (this.emitted[event])
61 callback.apply(undefined, this.emitted[event]);
63 this.emitter.on(event, callback);
70 * Keeps track of emitted events to run them on `on`.
74 Promise.prototype.safeEmit = function (event) {
75 // ensures a promise can't be fulfill() or reject() more than once
76 if (event == Promise.SUCCESS || event == Promise.FAILURE) {
77 if (this.emitted[Promise.SUCCESS] || this.emitted[Promise.FAILURE]) {
80 this.emitted[event] = toArray(arguments, 1);
83 this.emitter.emit.apply(this.emitter, arguments);
91 Promise.prototype.hasRejectListeners = function () {
92 return EventEmitter.listenerCount(this.emitter, Promise.FAILURE) > 0;
97 * Fulfills this promise with passed arguments.
99 * If this promise has already been fulfilled or rejected, no action is taken.
103 Promise.prototype.fulfill = function () {
104 return this.safeEmit.apply(this, strongUnshift(Promise.SUCCESS, arguments));
109 * Rejects this promise with `reason`.
111 * If this promise has already been fulfilled or rejected, no action is taken.
114 * @param {Object|String} reason
115 * @return {MPromise} this
117 Promise.prototype.reject = function (reason) {
118 if (this.ended && !this.hasRejectListeners())
120 return this.safeEmit(Promise.FAILURE, reason);
125 * Resolves this promise to a rejected state if `err` is passed or
126 * fulfilled state if no `err` is passed.
128 * @param {Error} [err] error or null
129 * @param {Object} [val] value to fulfill the promise with
132 Promise.prototype.resolve = function (err, val) {
133 if (err) return this.reject(err);
134 return this.fulfill(val);
139 * Adds a listener to the SUCCESS event.
141 * @return {MPromise} this
144 Promise.prototype.onFulfill = function (fn) {
145 if (!fn) return this;
146 if ('function' != typeof fn) throw new TypeError("fn should be a function");
147 return this.on(Promise.SUCCESS, fn);
152 * Adds a listener to the FAILURE event.
154 * @return {MPromise} this
157 Promise.prototype.onReject = function (fn) {
158 if (!fn) return this;
159 if ('function' != typeof fn) throw new TypeError("fn should be a function");
160 return this.on(Promise.FAILURE, fn);
165 * Adds a single function as a listener to both SUCCESS and FAILURE.
167 * It will be executed with traditional node.js argument position:
168 * function (err, args...) {}
170 * Also marks the promise as `end`ed, since it's the common use-case, and yet has no
171 * side effects unless `fn` is undefined or null.
173 * @param {Function} fn
174 * @return {MPromise} this
176 Promise.prototype.onResolve = function (fn) {
177 if (!fn) return this;
178 if ('function' != typeof fn) throw new TypeError("fn should be a function");
179 this.on(Promise.FAILURE, function (err) { fn.call(this, err); });
180 this.on(Promise.SUCCESS, function () { fn.apply(this, strongUnshift(null, arguments)); });
186 * Creates a new promise and returns it. If `onFulfill` or
187 * `onReject` are passed, they are added as SUCCESS/ERROR callbacks
188 * to this promise after the next tick.
190 * Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification. Read for more detail how to use this method.
194 * var p = new Promise;
195 * p.then(function (arg) {
197 * }).then(function (arg) {
198 * throw new Error(arg + ' is an error!');
199 * }).then(null, function (err) {
200 * assert.ok(err instanceof Error);
201 * assert.equal('2 is an error', err.message);
205 * @see promises-A+ https://github.com/promises-aplus/promises-spec
206 * @param {Function} onFulfill
207 * @param {Function} [onReject]
208 * @return {MPromise} newPromise
210 Promise.prototype.then = function (onFulfill, onReject) {
211 var newPromise = new Promise;
213 if ('function' == typeof onFulfill) {
214 this.onFulfill(handler(newPromise, onFulfill));
216 this.onFulfill(newPromise.fulfill.bind(newPromise));
219 if ('function' == typeof onReject) {
220 this.onReject(handler(newPromise, onReject));
222 this.onReject(newPromise.reject.bind(newPromise));
229 function handler(promise, fn) {
230 function newTickHandler() {
231 var pDomain = promise.emitter.domain;
232 if (pDomain && pDomain !== process.domain) pDomain.enter();
234 var x = fn.apply(undefined, boundHandler.args);
241 function boundHandler() {
242 boundHandler.args = arguments;
243 process.nextTick(newTickHandler);
249 function resolve(promise, x) {
250 function fulfillOnce() {
252 resolve.apply(undefined, strongUnshift(promise, arguments));
254 function rejectOnce(reason) {
256 promise.reject(reason);
260 promise.reject(new TypeError("promise and x are the same"));
263 var rest = toArray(arguments, 1);
265 if ('undefined' == type || null == x || !('object' == type || 'function' == type)) {
266 promise.fulfill.apply(promise, rest);
271 var theThen = x.then;
277 if ('function' != typeof theThen) {
278 promise.fulfill.apply(promise, rest);
284 var ret = theThen.call(x, fulfillOnce, rejectOnce);
294 * Signifies that this promise was the last in a chain of `then()s`: if a handler passed to the call to `then` which produced this promise throws, the exception will go uncaught.
298 * var p = new Promise;
299 * p.then(function(){ throw new Error('shucks') });
300 * setTimeout(function () {
302 * // error was caught and swallowed by the promise returned from
303 * // p.then(). we either have to always register handlers on
304 * // the returned promises or we can do the following...
307 * // this time we use .end() which prevents catching thrown errors
308 * var p = new Promise;
309 * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
310 * setTimeout(function () {
311 * p.fulfill(); // throws "shucks"
315 * @param {Function} [onReject]
316 * @return {MPromise} this
318 Promise.prototype.end = Promise.prototype['catch'] = function (onReject) {
319 if (!onReject && !this.hasRejectListeners())
320 onReject = function idRejector(e) { throw e; };
321 this.onReject(onReject);
328 * A debug utility function that adds handlers to a promise that will log some output to the `console`
332 * var p = new Promise;
333 * p.then(function(){ throw new Error('shucks') });
334 * setTimeout(function () {
336 * // error was caught and swallowed by the promise returned from
337 * // p.then(). we either have to always register handlers on
338 * // the returned promises or we can do the following...
341 * // this time we use .end() which prevents catching thrown errors
342 * var p = new Promise;
343 * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
344 * setTimeout(function () {
345 * p.fulfill(); // throws "shucks"
349 * @param {MPromise} p
350 * @param {String} name
351 * @return {MPromise} this
353 Promise.trace = function (p, name) {
356 console.log("%s fulfill %j", name, toArray(arguments));
359 console.log("%s reject %j", name, toArray(arguments));
365 Promise.prototype.chain = function (p2) {
367 p1.onFulfill(p2.fulfill.bind(p2));
368 p1.onReject(p2.reject.bind(p2));
373 Promise.prototype.all = function (promiseOfArr) {
374 var pRet = new Promise;
375 this.then(promiseOfArr).then(
376 function (promiseArr) {
380 if (!promiseArr.length) pRet.resolve();
381 promiseArr.forEach(function (promise, index) {
382 if (errSentinel) return;
386 if (errSentinel) return;
389 if (count == 0) pRet.fulfill(ret);
392 if (errSentinel) return;
400 , pRet.reject.bind(pRet)
406 Promise.hook = function (arr) {
407 var p1 = new Promise;
408 var pFinal = new Promise;
409 var signalP = function () {
417 arr.forEach(function (hook) {
422 hook(p.resolve.bind(p), signalP);
427 ps = ps.then(signalP);
433 /* This is for the A+ tests, but it's very useful as well */
434 Promise.fulfilled = function fulfilled() { var p = new Promise; p.fulfill.apply(p, arguments); return p; };
435 Promise.rejected = function rejected(reason) { return new Promise().reject(reason); };
436 Promise.deferred = function deferred() {
440 reject: p.reject.bind(p),
441 resolve: p.fulfill.bind(p),
442 callback: p.resolve.bind(p)
445 /* End A+ tests adapter bit */