// 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
) );