arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/node_modules/mpromise/lib/promise.js
node_modules/mongoose/node_modules/mpromise/lib/promise.jsRaw
  1'use strict';
  2var util = require('util');
  3var EventEmitter = require('events').EventEmitter;
  4function toArray(arr, start, end) {
  5  return Array.prototype.slice.call(arr, start, end)
  6}
  7function 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 */
 25function 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 */
 39module.exports = Promise;
 40
 41
 42/*!
 43 * event names
 44 */
 45Promise.SUCCESS = 'fulfill';
 46Promise.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 */
 59Promise.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 */
 74Promise.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 */
 91Promise.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 */
103Promise.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 */
117Promise.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 */
132Promise.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 */
144Promise.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 */
157Promise.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 */
176Promise.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 */
210Promise.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
229function 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
249function 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 */
318Promise.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 */
353Promise.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
365Promise.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
373Promise.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
406Promise.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 */
434Promise.fulfilled = function fulfilled() { var p = new Promise; p.fulfill.apply(p, arguments); return p; };
435Promise.rejected = function rejected(reason) { return new Promise().reject(reason); };
436Promise.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 */