API Docs for: 0.1.4
Show:

File: src/builtins/Promise.js

function isFunction(object) {
    return object && ((typeof object === 'function') && (object instanceof Function));
}

/**
 * An instance of a promise. Created and accessed through $q.
 *
 * @class Promise
 * @private
 * @constructor
 */
function Promise() {
    var resolveCallbacks = [];
    var rejectCallbacks = [];
    var notifyCallbacks = [];
    var state = 'pending';
    var resolution;
    var rejection;

    var api = {
        /**
         * Attach resolution, rejection and notification handlers to the promise.
         *
         * @method then
         * @for Promise
         * @chainable
         * @param {Function} onResolve Executed when the promise is resolved.
         *  If another promise is returned, the next promise in the chain is
         *  attached to the returned promise. If a value is returned, the next
         *  promise in the chain is resolved with the returned value immediately.
         * @param {Function} onReject Executed when the promise is rejected
         * @param {Function} onNotify Executed when the promise notified
         * @return {Promise}
         */
        then: function(onResolve, onReject, onNotify) {
            var promise = new Promise();

            if (((state === 'pending') || (state === 'resolved')) && isFunction(
                    onResolve)) {
                function resolveWrapper(resolution) {
                    var returnValue = onResolve(resolution);

                    if (returnValue && isFunction(returnValue.then)) {
                        returnValue.then(function(nextResolution) {
                            promise.resolve(nextResolution);
                        }, function(nextRejection) {
                            promise.reject(nextRejection);
                        });
                    } else {
                        promise.resolve(returnValue);
                    }
                }

                if (state === 'resolved') {
                    resolveWrapper(resolution);
                } else {
                    resolveCallbacks.push(resolveWrapper);
                }
            }

            if ((state === 'pending') || (state === 'rejected')) {
                function rejectionWrapper(rejectWith) {
                    if (isFunction(onReject)) {
                        onReject(rejectWith);
                    } else {
                        /*
                         * Stop rejecting the promise chain once the rejection
                         * has been handled.
                         */
                        promise.reject(rejectWith);
                    }
                }

                if (state === 'rejected') {
                    rejectionWrapper(rejection);
                } else {
                    rejectCallbacks.push(rejectionWrapper);
                }
            }

            notifyCallbacks.push(function(notifyWith) {
                if (isFunction(onNotify)) {
                    onNotify(notifyWith);
                }

                promise.notify(notifyWith);
            });

            return promise;
        },

        /**
         * Registers a rejection handler. Shorthand for `.then(_, onReject)`.
         *
         * @method catch
         * @for Promise
         * @chainable
         * @param onReject Executed when
         *  the promise is rejected. Receives the rejection reason as argument.
         * @return {Promise}
         */
        'catch': function(onReject) {
            return api.then(null, onReject);
        },

        /**
         * Send a notification to the promise.
         *
         * @method notify
         * @for Promise
         * @param {*} notifyWith Notification value
         */
        notify: function(notifyWith) {
            notifyCallbacks.forEach(function(callback) {
                callback(notifyWith);
            });
        },

        /**
         * Rejects the promise.
         *
         * @method reject
         * @for Promise
         * @param {*} rejectWith Rejection reason. Will be passed on to the
         *  rejection handlers
         */
        reject: function(rejectWith) {
            rejectCallbacks.forEach(function(callback) {
                callback(rejectWith);
            });

            state = 'rejected';
            rejection = rejectWith;
        },

        /**
         * Resolves the promise.
         *
         * @method resolve
         * @for Promise
         * @param {*} resolveWith This value is passed on to the resolution
         *  handlers attached to the promise.
         */
        resolve: function(resolveWith) {
            resolveCallbacks.forEach(function(callback) {
                callback(resolveWith);
            });

            state = 'resolved';
            resolution = resolveWith;
        }
    };

    return api;
}

/**
 * The deferred object that's wrapped by $q
 *
 * @class Deferred
 * @param {Function} init This callback is passed three arguments, `resolve`,
 *  `reject` and `notify` that respectively resolve, reject or notify the
 *  deferreds promise.
 * @constructor
 */
function Deferred(init) {
    var promise = new Promise();

    if (isFunction(init)) {
        init(promise.resolve, promise.reject, promise.notify);
    }

    return {
        /**
         * See {{#crossLink "Promise/resolve:method"}}the underlying
         * promises resolve{{/crossLink}} documentation.
         *
         * @method resolve
         * @for Deferred
         */
        resolve: promise.resolve,

        /**
         * See {{#crossLink "Promise/reject:method"}}the underlying
         * promises reject{{/crossLink}} documentation.
         *
         * @method reject
         * @for Deferred
         */
        reject: promise.reject,

        /**
         * See {{#crossLink "Promise/notify:method"}}the underlying
         * promises notify{{/crossLink}} documentation.
         *
         * @method notify
         * @for Deferred
         */
        notify: promise.notify,

        /**
         * @property {Promise} promise
         * @for Deferred
         */
        promise: promise
    };
}

/**
 * Creates and manages promises. Used by $http and $routing.
 *
 * $q is used to create a deferred object, which contains a promise. The
 * deferred is used to create and manage promises.
 *
 * A promise accepts resolution, rejection and notification handlers that are
 * executed when the promise itself is resolved, rejected or notified. The
 * handlers are attached to the promise via the {{#crossLink "Promise/then:method"}}
 * .then(){{/crossLink}} method.
 *
 * You can attach multiple handlers by calling .then() multiple times with
 * different handlers. In addition, you can chain .then() calls. In this case,
 * the return value from .then() is a new promise that's attached to the resolve
 * handler passed to .then(). This way you can return promises from your resolve
 * handler and the next .then() will wait until that promise is resolved to
 * continue. Usually used to do multiple asyncronous calls in sequence.
 *
 * @class $q
 * @param {Function} callback The callback to initialized the deferred object
 * with
 * @constructor
 * @return {Promise}
 */
function $q(callback) {
    return (new Deferred(callback)).promise;
}

/**
 * Create a new defer. This method requires no arguments, the returned defer has
 * the methods required to resolve/reject/notify the promise.
 *
 * @example
 *      let defer = $q.defer();
 *      defer.promise.then((name) => console.log('Hi ' + name));
 *      defer.resolve('John');
 *      //=> "Hi John"
 * @method defer
 * @for $q
 * @return {Deferred}
 */
$q.defer = function() {
    return new Deferred();
};

/**
 * Creates a new promise and resolves it with `value`. If `value` is a promise,
 * the returned promise is attached to `value`. If onResolve, onReject or
 * onNotify are given, they are attached to the new promise.
 *
 * @method when
 * @for $q
 * @example
 *      $q.when('John').then((name) => console.log('Hi ' + name));
 *      //=> "Hi John"
 * @param {*|Promise} value Value that the returned promise is resolve with. If
 *  value is a promise, the returned promise is attached to value.
 * @param {Function} [onResolve] Resolve handler
 * @param {Function} [onReject] Rejection handler
 * @param {Function} [onNotify] Notification handler
 * @return {Promise}
 */
$q.when = function(value, onResolve, onReject, onNotify) {
    var defer = new Deferred(function(resolve, reject, notify) {
        if (value && value.then) {
            value.then(function(resolveValue) {
                resolve(resolveValue);
            }, function(error) {
                reject(error);
            }, function(notifyValue) {
                notify(notifyValue);
            });
        } else {
            resolve(value);
        }
    });

    defer.promise.then(onResolve, onReject, onNotify);

    return defer.promise;
};

/**
 * Alias for {{#crossLink "$q/when:method"}}$q.when{{/crossLink}}
 * @method resolve
 * @for $q
 */
$q.resolve = $q.when;

/**
 * Takes an array of promises (called inner promises) and creates a new promise
 * (called outer promise) that resolves when all the inner promises resolve.
 * If any of the inner promises are rejected, the outer promise is
 * immediately rejected as well and any other inner promises left over are
 * discarded.
 *
 * E.g. if you have three inner promises, A, B, and C, then the outer promise O
 * is resolved once all three A, B and C are resolved.
 *
 * If A is resolved, and B is rejected, and C is pending, then O will be
 * rejected regardless of C's outcome.
 *
 * @method all
 * @example
 *      let greeting = $q.defer();
 *      let name = $q.defer();
 *
 *      $q.all([greeting.promise, name.promise])
 *          .then((greeting, name) => console.log(greeting + ' ' + name));
 *
 *      greeting.resolve('Welcome');
 *      name.resolve('John')
 *      //=> "Welcome John"
 * @param {Array} promises Array of promises
 * @return {Promise}
 */
$q.all = function(promises) {
    if (!(promises instanceof Array)) {
        throw new Error('Promises need to be passed to $q.all in an array');
    }

    var counter = 0;
    var resolutions = [];

    var deferred = new Deferred();

    function checkComplete() {
        if (counter === promises.length) {
            deferred.resolve(resolutions);
        }
    }

    promises.forEach(function(promise, index) {
        promise.then(function(resolution) {
            resolutions[index] = resolution;
            ++counter;
            checkComplete();
        }, function(rejection) {
            deferred.reject(rejection);
        });
    });

    return deferred.promise;
};

module.exports = function() {
    return $q;
};