3cb4419b2249f622fb411385d27954b85a7e6c06
[aai/esr-gui.git] /
1 'use strict';
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)
6 }
7 function strongUnshift(x, arrLike) {
8   var arr = toArray(arrLike);
9   arr.unshift(x);
10   return arr;
11 }
12
13
14 /**
15  * MPromise constructor.
16  *
17  * _NOTE: The success and failure event names can be overridden by setting `Promise.SUCCESS` and `Promise.FAILURE` respectively._
18  *
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)
23  * @api public
24  */
25 function Promise(back) {
26   this.emitter = new EventEmitter();
27   this.emitted = {};
28   this.ended = false;
29   if ('function' == typeof back) {
30     this.ended = true;
31     this.onResolve(back);
32   }
33 }
34
35
36 /*
37  * Module exports.
38  */
39 module.exports = Promise;
40
41
42 /*!
43  * event names
44  */
45 Promise.SUCCESS = 'fulfill';
46 Promise.FAILURE = 'reject';
47
48
49 /**
50  * Adds `listener` to the `event`.
51  *
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.
53  *
54  * @param {String} event
55  * @param {Function} callback
56  * @return {MPromise} this
57  * @api private
58  */
59 Promise.prototype.on = function (event, callback) {
60   if (this.emitted[event])
61     callback.apply(undefined, this.emitted[event]);
62   else
63     this.emitter.on(event, callback);
64
65   return this;
66 };
67
68
69 /**
70  * Keeps track of emitted events to run them on `on`.
71  *
72  * @api private
73  */
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]) {
78       return this;
79     }
80     this.emitted[event] = toArray(arguments, 1);
81   }
82
83   this.emitter.emit.apply(this.emitter, arguments);
84   return this;
85 };
86
87
88 /**
89  * @api private
90  */
91 Promise.prototype.hasRejectListeners = function () {
92   return EventEmitter.listenerCount(this.emitter, Promise.FAILURE) > 0;
93 };
94
95
96 /**
97  * Fulfills this promise with passed arguments.
98  *
99  * If this promise has already been fulfilled or rejected, no action is taken.
100  *
101  * @api public
102  */
103 Promise.prototype.fulfill = function () {
104   return this.safeEmit.apply(this, strongUnshift(Promise.SUCCESS, arguments));
105 };
106
107
108 /**
109  * Rejects this promise with `reason`.
110  *
111  * If this promise has already been fulfilled or rejected, no action is taken.
112  *
113  * @api public
114  * @param {Object|String} reason
115  * @return {MPromise} this
116  */
117 Promise.prototype.reject = function (reason) {
118   if (this.ended && !this.hasRejectListeners())
119     throw reason;
120   return this.safeEmit(Promise.FAILURE, reason);
121 };
122
123
124 /**
125  * Resolves this promise to a rejected state if `err` is passed or
126  * fulfilled state if no `err` is passed.
127  *
128  * @param {Error} [err] error or null
129  * @param {Object} [val] value to fulfill the promise with
130  * @api public
131  */
132 Promise.prototype.resolve = function (err, val) {
133   if (err) return this.reject(err);
134   return this.fulfill(val);
135 };
136
137
138 /**
139  * Adds a listener to the SUCCESS event.
140  *
141  * @return {MPromise} this
142  * @api public
143  */
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);
148 };
149
150
151 /**
152  * Adds a listener to the FAILURE event.
153  *
154  * @return {MPromise} this
155  * @api public
156  */
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);
161 };
162
163
164 /**
165  * Adds a single function as a listener to both SUCCESS and FAILURE.
166  *
167  * It will be executed with traditional node.js argument position:
168  * function (err, args...) {}
169  *
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.
172  *
173  * @param {Function} fn
174  * @return {MPromise} this
175  */
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)); });
181   return this;
182 };
183
184
185 /**
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.
189  *
190  * Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification. Read for more detail how to use this method.
191  *
192  * ####Example:
193  *
194  *     var p = new Promise;
195  *     p.then(function (arg) {
196  *       return arg + 1;
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);
202  *     });
203  *     p.complete(1);
204  *
205  * @see promises-A+ https://github.com/promises-aplus/promises-spec
206  * @param {Function} onFulfill
207  * @param {Function} [onReject]
208  * @return {MPromise} newPromise
209  */
210 Promise.prototype.then = function (onFulfill, onReject) {
211   var newPromise = new Promise;
212
213   if ('function' == typeof onFulfill) {
214     this.onFulfill(handler(newPromise, onFulfill));
215   } else {
216     this.onFulfill(newPromise.fulfill.bind(newPromise));
217   }
218
219   if ('function' == typeof onReject) {
220     this.onReject(handler(newPromise, onReject));
221   } else {
222     this.onReject(newPromise.reject.bind(newPromise));
223   }
224
225   return newPromise;
226 };
227
228
229 function handler(promise, fn) {
230   function newTickHandler() {
231     var pDomain = promise.emitter.domain;
232     if (pDomain && pDomain !== process.domain) pDomain.enter();
233     try {
234       var x = fn.apply(undefined, boundHandler.args);
235     } catch (err) {
236       promise.reject(err);
237       return;
238     }
239     resolve(promise, x);
240   }
241   function boundHandler() {
242     boundHandler.args = arguments;
243     process.nextTick(newTickHandler);
244   }
245   return boundHandler;
246 }
247
248
249 function resolve(promise, x) {
250   function fulfillOnce() {
251     if (done++) return;
252     resolve.apply(undefined, strongUnshift(promise, arguments));
253   }
254   function rejectOnce(reason) {
255     if (done++) return;
256     promise.reject(reason);
257   }
258
259   if (promise === x) {
260     promise.reject(new TypeError("promise and x are the same"));
261     return;
262   }
263   var rest = toArray(arguments, 1);
264   var type = typeof x;
265   if ('undefined' == type || null == x || !('object' == type || 'function' == type)) {
266     promise.fulfill.apply(promise, rest);
267     return;
268   }
269
270   try {
271     var theThen = x.then;
272   } catch (err) {
273     promise.reject(err);
274     return;
275   }
276
277   if ('function' != typeof theThen) {
278     promise.fulfill.apply(promise, rest);
279     return;
280   }
281
282   var done = 0;
283   try {
284     var ret = theThen.call(x, fulfillOnce, rejectOnce);
285     return ret;
286   } catch (err) {
287     if (done++) return;
288     promise.reject(err);
289   }
290 }
291
292
293 /**
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.
295  *
296  * ####Example:
297  *
298  *     var p = new Promise;
299  *     p.then(function(){ throw new Error('shucks') });
300  *     setTimeout(function () {
301  *       p.fulfill();
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...
305  *     }, 10);
306  *
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"
312  *     }, 10);
313  *
314  * @api public
315  * @param {Function} [onReject]
316  * @return {MPromise} this
317  */
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);
322   this.ended = true;
323   return this;
324 };
325
326
327 /**
328  * A debug utility function that adds handlers to a promise that will log some output to the `console`
329  *
330  * ####Example:
331  *
332  *     var p = new Promise;
333  *     p.then(function(){ throw new Error('shucks') });
334  *     setTimeout(function () {
335  *       p.fulfill();
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...
339  *     }, 10);
340  *
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"
346  *     }, 10);
347  *
348  * @api public
349  * @param {MPromise} p
350  * @param {String} name
351  * @return {MPromise} this
352  */
353 Promise.trace = function (p, name) {
354   p.then(
355     function () {
356       console.log("%s fulfill %j", name, toArray(arguments));
357     },
358     function () {
359       console.log("%s reject %j", name, toArray(arguments));
360     }
361   )
362 };
363
364
365 Promise.prototype.chain = function (p2) {
366   var p1 = this;
367   p1.onFulfill(p2.fulfill.bind(p2));
368   p1.onReject(p2.reject.bind(p2));
369   return p2;
370 };
371
372
373 Promise.prototype.all = function (promiseOfArr) {
374   var pRet = new Promise;
375   this.then(promiseOfArr).then(
376     function (promiseArr) {
377       var count = 0;
378       var ret = [];
379       var errSentinel;
380       if (!promiseArr.length) pRet.resolve();
381       promiseArr.forEach(function (promise, index) {
382         if (errSentinel) return;
383         count++;
384         promise.then(
385           function (val) {
386             if (errSentinel) return;
387             ret[index] = val;
388             --count;
389             if (count == 0) pRet.fulfill(ret);
390           },
391           function (err) {
392             if (errSentinel) return;
393             errSentinel = err;
394             pRet.reject(err);
395           }
396         );
397       });
398       return pRet;
399     }
400     , pRet.reject.bind(pRet)
401   );
402   return pRet;
403 };
404
405
406 Promise.hook = function (arr) {
407   var p1 = new Promise;
408   var pFinal = new Promise;
409   var signalP = function () {
410     --count;
411     if (count == 0)
412       pFinal.fulfill();
413     return pFinal;
414   };
415   var count = 1;
416   var ps = p1;
417   arr.forEach(function (hook) {
418     ps = ps.then(
419       function () {
420         var p = new Promise;
421         count++;
422         hook(p.resolve.bind(p), signalP);
423         return p;
424       }
425     )
426   });
427   ps = ps.then(signalP);
428   p1.resolve();
429   return ps;
430 };
431
432
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() {
437   var p = new Promise;
438   return {
439     promise: p,
440     reject: p.reject.bind(p),
441     resolve: p.fulfill.bind(p),
442     callback: p.resolve.bind(p)
443   }
444 };
445 /* End A+ tests adapter bit */