Source: polyfill/Promise.js

// Licensed Materials - Property of IBM
//
// IBM Watson Analytics
//
// (C) Copyright IBM Corp. 2015
//
// US Government Users Restricted Rights - Use, duplication or
// disclosure restricted by GSA ADP Schedule Contract with IBM Corp.

module.exports = ( function(
        ObjectPolyfill,
        setImmediate
    )
{
    "use strict";

    /*jshint latedef:false*/

    /**
     * Helper method that turns an _iterable into an array of promises
     * @memberof module:barejs/polyfill.Promise~
     * @private
     */
    function iterableToPromiseArray( _iterable )
    {
        var promises;
        var it;
        var value;

        if ( _iterable.length || _iterable.length === 0 )
        {
            promises = new Array( _iterable.length );
            for ( var i = 0, len = _iterable.length; i < len; ++i )
            {
                value = _iterable[i];
                if ( ( !value ) || ( typeof value.then !== "function" ) )
                    value = Promise.resolve( value );
                promises[i] = value;
            }
        }
        else if ( ( it = ObjectPolyfill.getIterator( _iterable ) ) )
        {
            promises = [];
            for( value = it.next(); !value.done; value = it.next() )
            {
                if ( ( !value.value ) || ( typeof value.value.then !== "function" ) )
                    promises.push( Promise.resolve( value.value ) );
                else
                    promises.push( value.value );
            }
        }
        else
        {
            throw new Error( "Invalid iterable" );
        }

        return promises;
    }

    /**
     * Handler for a promise
     * @class module:barejs/polyfill.Promise~Handler
     * @private
     */
    function Handler( _onFulfilled, _onRejected, _promise )
    {
        this.onFulfilled = typeof _onFulfilled === "function" ? _onFulfilled : null;
        this.onRejected = typeof _onRejected === "function" ? _onRejected : null;
        this.promise = _promise;
    }

    /**
     * No-operation function
     */
    function noop() {}

    // States:
    //
    // 0 - pending
    // 1 - fulfilled with _value
    // 2 - rejected with _value
    // 3 - adopted the state of another promise, _value
    //
    // once the state is no longer pending (0) it is immutable

    // All `_` prefixed properties will be reduced to `_{random number}`
    // at build time to obfuscate them and discourage their use.
    // We don't use symbols or Object.defineProperty to fully hide them
    // because the performance isn't good enough.

    // to avoid using try/catch inside critical functions, we
    // extract them to here.
    var LAST_ERROR = null;
    var IS_ERROR = {};

    /**
     * Safely request the then function from an object
     */
    function getThen( _obj )
    {
        try
        {
            return _obj.then;
        }
        catch ( ex )
        {
            LAST_ERROR = ex;
            return IS_ERROR;
        }
    }

    function tryCallOne( _fn, _a )
    {
        try
        {
            return _fn( _a );
        }
        catch ( ex )
        {
            LAST_ERROR = ex;
            return IS_ERROR;
        }
    }
    function tryCallTwo( _fn, _a, _b )
    {
        try
        {
            _fn( _a, _b );
        }
        catch ( ex )
        {
            LAST_ERROR = ex;
            return IS_ERROR;
        }
    }

    /**
     * Create a new Promise
     * @class module:barejs/polyfill.Promise
     * @param {function} _resolver The resolver function that will be called with two callbacks:
     * _resolve (call on succes) and _reject (call on failure).
     */
    function Promise( _resolver )
    {
        if ( typeof this !== "object" )
            throw new TypeError( "Promises must be constructed via new" );

        this._state = 0;
        this._value = null;
        this._deferreds = [];
        if ( ObjectPolyfill.ensureCallable( _resolver ) === noop )
            return;
        doResolve( _resolver, this );
    }

    function safeThen( _self, _onFulfilled, _onRejected )
    {
        return new _self.constructor( function( resolve, reject )
        {
            var res = new Promise( noop );
            res.then( resolve, reject );
            handle( _self, new Handler( _onFulfilled, _onRejected, res ) );
        } );
    }

    function handle( _self, _deferred )
    {
        while ( _self._state === 3 )
        {
            _self = _self._value;
        }
        if ( _self._state === 0 )
        {
            _self._deferreds.push( _deferred );
            return;
        }

        setImmediate( function()
        {
            var cb = _self._state === 1 ? _deferred.onFulfilled : _deferred.onRejected;
            if ( cb === null )
            {
                if ( _self._state === 1 )
                {
                    resolve( _deferred.promise, _self._value );
                }
                else
                {
                    reject( _deferred.promise, _self._value );
                }
                return;
            }
            var ret = tryCallOne( cb, _self._value );
            if ( ret === IS_ERROR )
            {
                reject( _deferred.promise, LAST_ERROR );
            }
            else
            {
                resolve( _deferred.promise, ret );
            }
        } );
    }

    function resolve( _self, _newValue )
    {
        // Promise Resolution Procedure:
        // https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
        if ( _newValue === _self )
        {
            return reject( _self, new TypeError( 'A promise cannot be resolved with itself.' ) );
        }
        if ( _newValue && (typeof _newValue === "object" || typeof _newValue === "function") )
        {
            var then = getThen( _newValue );
            if ( then === IS_ERROR )
            {
                return reject( _self, LAST_ERROR );
            }
            if ( then === _self.then && ( _newValue instanceof Promise ) )
            {
                _self._state = 3;
                _self._value = _newValue;
                finale( _self );
                return;
            }
            else if ( typeof then === "function" )
            {
                doResolve( then.bind( _newValue ), _self );
                return;
            }
        }
        _self._state = 1;
        _self._value = _newValue;
        finale( _self );
    }

    function reject( _self, _newValue )
    {
        _self._state = 2;
        _self._value = _newValue;
        finale( _self );
    }

    function finale( _self )
    {
        for ( var i = 0; i < _self._deferreds.length; ++i )
            handle( _self, _self._deferreds[ i ] );

        _self._deferreds = null;
    }

    /**
     * Take a potentially misbehaving resolver function and make sure onFulfilled and onRejected
     * are only called once.
     *
     * Makes no guarantees about asynchrony.
     * @memberof module:barejs/polyfill.Promise~
     * @private
     */
    function doResolve( _resolver, _promise )
    {
        var done = false;
        var res = tryCallTwo( _resolver, function( value )
        {
            if ( done )
                return;
            done = true;
            resolve( _promise, value );
        }, function( reason )
        {
            if ( done )
                return;
            done = true;
            reject( _promise, reason );
        } );
        if ( !done && res === IS_ERROR )
        {
            done = true;
            reject( _promise, LAST_ERROR );
        }
    }

    return ObjectPolyfill.polyfill( Promise,
    /** @lends module:barejs/polyfill.Promise */
    {
        _noop: noop,

        /**
         * The Promise.resolve( _value ) method returns a Promise object that is resolved with the given value.
         * If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable,
         * adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
         * @param {*} _value The value to resolve with.
         * @returns {module:barejs/polyfill.Promise} A Promise that is resolved with _value.
         */
        resolve: function resolve( _value )
        {
            return new Promise( function( _resolve, _reject )
            {
                _resolve( _value );
            } );
        },

        /**
         * The Promise.reject( _reason ) method returns a Promise object that is rejected with the given reason.
         * @param {*} _reason The rejection reason (passed as rejection argument).
         * @returns {module:barejs/polyfill.Promise} A Promise that is rejected with _reason.
         */
        reject: function reject( _reason )
        {
            return new Promise( function( _resolve, _reject )
            {
                _reject( _reason );
            } );
        },

        /**
         * The Promise.all( _iterable ) method returns a promise that resolves when all of the promises
         * in the iterable argument have resolved. If any of the passed in promises rejects, the all
         * Promise immediately rejects with the value of the promise that rejected, discarding all the
         * other promises whether or not they have resolved.
         * @param _iterable {Object} Array that can be iterated.
         * @returns {module:barejs/polyfill.Promise} A promise that will resolve with an array of values corresponding to all Promises in _iterable, after every Promise is resolved.
         */
        all: function all( _iterable )
        {
            var promises = iterableToPromiseArray( _iterable );

            if ( promises.length < 1 )
                return Promise.resolve( promises );

            return new Promise( function( _resolve, _reject )
            {
                var values = new Array( promises.length );
                var resolveCount = 0;

                function rejected( _reason )
                {
                    if ( values )
                    {
                        _reject( _reason );
                        values = null;
                    }
                }

                function resolved( _index, _value )
                {
                    if ( values )
                    {
                        // Promises should never resolve twice, so we don't perform any sanity checking
                        // to see if values.hasOwnProperty( _index )...
                        values[_index] = _value;
                        if ( ++resolveCount >= values.length )
                        {
                            _resolve( values );
                            values = null;
                        }
                    }
                }

                for ( var i = 0, len = promises.length; i < len; ++i )
                    promises[i].then( resolved.bind( null, i ), rejected );
            } );
        },

        /**
         * The Promise.race( _iterable ) method returns a promise that resolves or rejects as soon as one
         * of the promises in the iterable resolves or rejects, with the value or reason from that promise.
         * @returns {module:barejs/polyfill.Promise} A promise that will resolve with the value of the first Promise to resolve.
         */
        race: function race( _iterable )
        {
            var promises = iterableToPromiseArray( _iterable );

            return new Promise( function( _resolve, _reject )
            {
                var isResolved = false;

                function rejected( _reason )
                {
                    if ( !isResolved )
                    {
                        _reject( _reason );
                        isResolved = true;
                    }
                }

                function resolved( _value )
                {
                    if ( !isResolved )
                    {
                        _resolve( _value );
                        isResolved = true;
                    }
                }

                for ( var i = 0, len = promises.length; i < len; ++i )
                    promises[i].then( resolved, rejected );
            } );
        }
    },
    /** @lends module:barejs/polyfill.Promise# */
    {
        /**
         * Register either a resolve or reject callback, or both.
         * @returns {module:barejs/polyfill.Promise} A promise that will resolve or reject with the value returned by (or thrown from) _onFulfilled or _onRejected.
         */
        then: function( _onFulfilled, _onRejected )
        {
            if ( this.constructor !== Promise )
                return safeThen( this, _onFulfilled, _onRejected );

            var res = new Promise( noop );
            handle( this, new Handler( _onFulfilled, _onRejected, res ) );
            return res;
        },

        /**
         * Register a rejection callback (shortcut for then( null, _onRejected ) ).
         * @returns {module:barejs/polyfill.Promise} A promise that will resolve or reject with the value returned by (or thrown from) _onRejected.
         */
        "catch": function( _onRejected )
        {
            return this.then( null, _onRejected );
        }
    }, null, "Promise" );
}(
    require( "./Object" ),
    /* global setImmediate, setTimeout */
    typeof setImmediate !== "undefined" ? setImmediate : setTimeout
) );