123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- 'use strict';
- var util = require('util');
- var EventEmitter = require('events').EventEmitter;
- function toArray(arr, start, end) {
- return Array.prototype.slice.call(arr, start, end)
- }
- function strongUnshift(x, arrLike) {
- var arr = toArray(arrLike);
- arr.unshift(x);
- return arr;
- }
- /**
- * MPromise constructor.
- *
- * _NOTE: The success and failure event names can be overridden by setting `Promise.SUCCESS` and `Promise.FAILURE` respectively._
- *
- * @param {Function} back a function that accepts `fn(err, ...){}` as signature
- * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
- * @event `reject`: Emits when the promise is rejected (event name may be overridden)
- * @event `fulfill`: Emits when the promise is fulfilled (event name may be overridden)
- * @api public
- */
- function Promise(back) {
- this.emitter = new EventEmitter();
- this.emitted = {};
- this.ended = false;
- if ('function' == typeof back) {
- this.ended = true;
- this.onResolve(back);
- }
- }
- /*
- * Module exports.
- */
- module.exports = Promise;
- /*!
- * event names
- */
- Promise.SUCCESS = 'fulfill';
- Promise.FAILURE = 'reject';
- /**
- * Adds `listener` to the `event`.
- *
- * 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.
- *
- * @param {String} event
- * @param {Function} callback
- * @return {MPromise} this
- * @api private
- */
- Promise.prototype.on = function (event, callback) {
- if (this.emitted[event])
- callback.apply(undefined, this.emitted[event]);
- else
- this.emitter.on(event, callback);
- return this;
- };
- /**
- * Keeps track of emitted events to run them on `on`.
- *
- * @api private
- */
- Promise.prototype.safeEmit = function (event) {
- // ensures a promise can't be fulfill() or reject() more than once
- if (event == Promise.SUCCESS || event == Promise.FAILURE) {
- if (this.emitted[Promise.SUCCESS] || this.emitted[Promise.FAILURE]) {
- return this;
- }
- this.emitted[event] = toArray(arguments, 1);
- }
- this.emitter.emit.apply(this.emitter, arguments);
- return this;
- };
- /**
- * @api private
- */
- Promise.prototype.hasRejectListeners = function () {
- return EventEmitter.listenerCount(this.emitter, Promise.FAILURE) > 0;
- };
- /**
- * Fulfills this promise with passed arguments.
- *
- * If this promise has already been fulfilled or rejected, no action is taken.
- *
- * @api public
- */
- Promise.prototype.fulfill = function () {
- return this.safeEmit.apply(this, strongUnshift(Promise.SUCCESS, arguments));
- };
- /**
- * Rejects this promise with `reason`.
- *
- * If this promise has already been fulfilled or rejected, no action is taken.
- *
- * @api public
- * @param {Object|String} reason
- * @return {MPromise} this
- */
- Promise.prototype.reject = function (reason) {
- if (this.ended && !this.hasRejectListeners())
- throw reason;
- return this.safeEmit(Promise.FAILURE, reason);
- };
- /**
- * Resolves this promise to a rejected state if `err` is passed or
- * fulfilled state if no `err` is passed.
- *
- * @param {Error} [err] error or null
- * @param {Object} [val] value to fulfill the promise with
- * @api public
- */
- Promise.prototype.resolve = function (err, val) {
- if (err) return this.reject(err);
- return this.fulfill(val);
- };
- /**
- * Adds a listener to the SUCCESS event.
- *
- * @return {MPromise} this
- * @api public
- */
- Promise.prototype.onFulfill = function (fn) {
- if (!fn) return this;
- if ('function' != typeof fn) throw new TypeError("fn should be a function");
- return this.on(Promise.SUCCESS, fn);
- };
- /**
- * Adds a listener to the FAILURE event.
- *
- * @return {MPromise} this
- * @api public
- */
- Promise.prototype.onReject = function (fn) {
- if (!fn) return this;
- if ('function' != typeof fn) throw new TypeError("fn should be a function");
- return this.on(Promise.FAILURE, fn);
- };
- /**
- * Adds a single function as a listener to both SUCCESS and FAILURE.
- *
- * It will be executed with traditional node.js argument position:
- * function (err, args...) {}
- *
- * Also marks the promise as `end`ed, since it's the common use-case, and yet has no
- * side effects unless `fn` is undefined or null.
- *
- * @param {Function} fn
- * @return {MPromise} this
- */
- Promise.prototype.onResolve = function (fn) {
- if (!fn) return this;
- if ('function' != typeof fn) throw new TypeError("fn should be a function");
- this.on(Promise.FAILURE, function (err) { fn.call(this, err); });
- this.on(Promise.SUCCESS, function () { fn.apply(this, strongUnshift(null, arguments)); });
- return this;
- };
- /**
- * Creates a new promise and returns it. If `onFulfill` or
- * `onReject` are passed, they are added as SUCCESS/ERROR callbacks
- * to this promise after the next tick.
- *
- * Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification. Read for more detail how to use this method.
- *
- * ####Example:
- *
- * var p = new Promise;
- * p.then(function (arg) {
- * return arg + 1;
- * }).then(function (arg) {
- * throw new Error(arg + ' is an error!');
- * }).then(null, function (err) {
- * assert.ok(err instanceof Error);
- * assert.equal('2 is an error', err.message);
- * });
- * p.complete(1);
- *
- * @see promises-A+ https://github.com/promises-aplus/promises-spec
- * @param {Function} onFulfill
- * @param {Function} [onReject]
- * @return {MPromise} newPromise
- */
- Promise.prototype.then = function (onFulfill, onReject) {
- var newPromise = new Promise;
- if ('function' == typeof onFulfill) {
- this.onFulfill(handler(newPromise, onFulfill));
- } else {
- this.onFulfill(newPromise.fulfill.bind(newPromise));
- }
- if ('function' == typeof onReject) {
- this.onReject(handler(newPromise, onReject));
- } else {
- this.onReject(newPromise.reject.bind(newPromise));
- }
- return newPromise;
- };
- function handler(promise, fn) {
- function newTickHandler() {
- var pDomain = promise.emitter.domain;
- if (pDomain && pDomain !== process.domain) pDomain.enter();
- try {
- var x = fn.apply(undefined, boundHandler.args);
- } catch (err) {
- promise.reject(err);
- return;
- }
- resolve(promise, x);
- }
- function boundHandler() {
- boundHandler.args = arguments;
- process.nextTick(newTickHandler);
- }
- return boundHandler;
- }
- function resolve(promise, x) {
- function fulfillOnce() {
- if (done++) return;
- resolve.apply(undefined, strongUnshift(promise, arguments));
- }
- function rejectOnce(reason) {
- if (done++) return;
- promise.reject(reason);
- }
- if (promise === x) {
- promise.reject(new TypeError("promise and x are the same"));
- return;
- }
- var rest = toArray(arguments, 1);
- var type = typeof x;
- if ('undefined' == type || null == x || !('object' == type || 'function' == type)) {
- promise.fulfill.apply(promise, rest);
- return;
- }
- try {
- var theThen = x.then;
- } catch (err) {
- promise.reject(err);
- return;
- }
- if ('function' != typeof theThen) {
- promise.fulfill.apply(promise, rest);
- return;
- }
- var done = 0;
- try {
- var ret = theThen.call(x, fulfillOnce, rejectOnce);
- return ret;
- } catch (err) {
- if (done++) return;
- promise.reject(err);
- }
- }
- /**
- * 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.
- *
- * ####Example:
- *
- * var p = new Promise;
- * p.then(function(){ throw new Error('shucks') });
- * setTimeout(function () {
- * p.fulfill();
- * // error was caught and swallowed by the promise returned from
- * // p.then(). we either have to always register handlers on
- * // the returned promises or we can do the following...
- * }, 10);
- *
- * // this time we use .end() which prevents catching thrown errors
- * var p = new Promise;
- * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
- * setTimeout(function () {
- * p.fulfill(); // throws "shucks"
- * }, 10);
- *
- * @api public
- * @param {Function} [onReject]
- * @return {MPromise} this
- */
- Promise.prototype.end = Promise.prototype['catch'] = function (onReject) {
- if (!onReject && !this.hasRejectListeners())
- onReject = function idRejector(e) { throw e; };
- this.onReject(onReject);
- this.ended = true;
- return this;
- };
- /**
- * A debug utility function that adds handlers to a promise that will log some output to the `console`
- *
- * ####Example:
- *
- * var p = new Promise;
- * p.then(function(){ throw new Error('shucks') });
- * setTimeout(function () {
- * p.fulfill();
- * // error was caught and swallowed by the promise returned from
- * // p.then(). we either have to always register handlers on
- * // the returned promises or we can do the following...
- * }, 10);
- *
- * // this time we use .end() which prevents catching thrown errors
- * var p = new Promise;
- * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
- * setTimeout(function () {
- * p.fulfill(); // throws "shucks"
- * }, 10);
- *
- * @api public
- * @param {MPromise} p
- * @param {String} name
- * @return {MPromise} this
- */
- Promise.trace = function (p, name) {
- p.then(
- function () {
- console.log("%s fulfill %j", name, toArray(arguments));
- },
- function () {
- console.log("%s reject %j", name, toArray(arguments));
- }
- )
- };
- Promise.prototype.chain = function (p2) {
- var p1 = this;
- p1.onFulfill(p2.fulfill.bind(p2));
- p1.onReject(p2.reject.bind(p2));
- return p2;
- };
- Promise.prototype.all = function (promiseOfArr) {
- var pRet = new Promise;
- this.then(promiseOfArr).then(
- function (promiseArr) {
- var count = 0;
- var ret = [];
- var errSentinel;
- if (!promiseArr.length) pRet.resolve();
- promiseArr.forEach(function (promise, index) {
- if (errSentinel) return;
- count++;
- promise.then(
- function (val) {
- if (errSentinel) return;
- ret[index] = val;
- --count;
- if (count == 0) pRet.fulfill(ret);
- },
- function (err) {
- if (errSentinel) return;
- errSentinel = err;
- pRet.reject(err);
- }
- );
- });
- return pRet;
- }
- , pRet.reject.bind(pRet)
- );
- return pRet;
- };
- Promise.hook = function (arr) {
- var p1 = new Promise;
- var pFinal = new Promise;
- var signalP = function () {
- --count;
- if (count == 0)
- pFinal.fulfill();
- return pFinal;
- };
- var count = 1;
- var ps = p1;
- arr.forEach(function (hook) {
- ps = ps.then(
- function () {
- var p = new Promise;
- count++;
- hook(p.resolve.bind(p), signalP);
- return p;
- }
- )
- });
- ps = ps.then(signalP);
- p1.resolve();
- return ps;
- };
- /* This is for the A+ tests, but it's very useful as well */
- Promise.fulfilled = function fulfilled() { var p = new Promise; p.fulfill.apply(p, arguments); return p; };
- Promise.rejected = function rejected(reason) { return new Promise().reject(reason); };
- Promise.deferred = function deferred() {
- var p = new Promise;
- return {
- promise: p,
- reject: p.reject.bind(p),
- resolve: p.fulfill.bind(p),
- callback: p.resolve.bind(p)
- }
- };
- /* End A+ tests adapter bit */
|