UNPKG

bluebird

Version:

Full featured Promises/A+ implementation with exceptionally good performance

321 lines (293 loc) 10.5 kB
/** * Copyright (c) 2013 Petka Antonov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions:</p> * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ "use strict"; module.exports = function( Promise ) { var ASSERT = require("./assert.js"); var async = require( "./async.js" ); var util = require( "./util.js" ); var isPrimitive = util.isPrimitive; var errorObj = util.errorObj; var isObject = util.isObject; var tryCatch2 = util.tryCatch2; function Thenable() { this.errorObj = errorObj; this.__id__ = 0; this.treshold = 1000; this.thenableCache = new Array( this.treshold ); this.promiseCache = new Array( this.treshold ); this._compactQueued = false; } Thenable.prototype.couldBe = function Thenable$couldBe( ret ) { if( isPrimitive( ret ) ) { return false; } var id = ret.__id_$thenable__; if( typeof id === "number" && this.thenableCache[id] !== void 0 ) { return true; } return ("then" in ret); }; Thenable.prototype.is = function Thenable$is( ret, ref ) { var id = ret.__id_$thenable__; if( typeof id === "number" && this.thenableCache[id] !== void 0 ) { ref.ref = this.thenableCache[id]; ref.promise = this.promiseCache[id]; return true; } return this._thenableSlowCase( ret, ref ); }; Thenable.prototype.addCache = function Thenable$_addCache( thenable, promise ) { var id = this.__id__; this.__id__ = id + 1; var descriptor = this._descriptor( id ); Object.defineProperty( thenable, "__id_$thenable__", descriptor ); this.thenableCache[id] = thenable; this.promiseCache[id] = promise; if( this.thenableCache.length > this.treshold && !this._compactQueued) { this._compactQueued = true; async.invokeLater( this._compactCache, this, void 0 ); } }; Thenable.prototype.deleteCache = function Thenable$deleteCache( thenable ) { var id = thenable.__id_$thenable__; if( id === -1 ) { return; } this.thenableCache[id] = void 0; this.promiseCache[id] = void 0; thenable.__id_$thenable__ = -1; }; var descriptor = { value: 0, enumerable: false, writable: true, configurable: true }; Thenable.prototype._descriptor = function Thenable$_descriptor( id ) { descriptor.value = id; return descriptor; }; Thenable.prototype._compactCache = function Thenable$_compactCache() { var arr = this.thenableCache; var promiseArr = this.promiseCache; var skips = 0; var j = 0; for( var i = 0, len = arr.length; i < len; ++i ) { var item = arr[ i ]; if( item === void 0 ) { skips++; } else { promiseArr[ j ] = promiseArr[ i ]; item.__id_$thenable__ = j; arr[ j++ ] = item; } } var newId = arr.length - skips; if( newId === this.__id__ ) { this.treshold *= 2; } else for( var i = newId, len = arr.length; i < len; ++i ) { promiseArr[ j ] = arr[ i ] = void 0; } this.__id__ = newId; this._compactQueued = false; }; Thenable.prototype._thenableSlowCase = function Thenable$_thenableSlowCase( ret, ref ) { try { var then = ret.then; if( typeof then === "function" ) { ref.ref = then; return true; } return false; } catch(e) { this.errorObj.e = e; ref.ref = this.errorObj; return true; } }; var thenable = new Thenable( errorObj ); Promise._couldBeThenable = function( val ) { return thenable.couldBe( val ); }; function doThenable( obj, ref, caller ) { if( ref.promise != null ) { return ref.promise; } var resolver = Promise.pending( caller ); var result = ref.ref; if( result === errorObj ) { resolver.reject( result.e ); return resolver.promise; } thenable.addCache( obj, resolver.promise ); var called = false; var ret = tryCatch2( result, obj, function t( a ) { if( called ) return; called = true; thenable.deleteCache(obj); var b = Promise$_Cast( a ); if( b === a ) { resolver.fulfill( a ); } else { if( a === obj ) { resolver.promise._resolveFulfill( a ); } else { b._then( resolver.fulfill, resolver.reject, void 0, resolver, void 0, t ); } } }, function t( a ) { if( called ) return; called = true; thenable.deleteCache(obj); resolver.reject( a ); }); if( ret === errorObj && !called ) { resolver.reject( ret.e ); thenable.deleteCache(obj); } return resolver.promise; } function Promise$_Cast( obj, caller ) { if( isObject( obj ) ) { if( obj instanceof Promise ) { return obj; } var ref = { ref: null, promise: null }; if( thenable.is( obj, ref ) ) { caller = typeof caller === "function" ? caller : Promise$_Cast; return doThenable( obj, ref, caller ); } } return obj; } Promise.prototype._resolveThenable = function Promise$_resolveThenable( x, ref ) { if( ref.promise != null ) { this._assumeStateOf( ref.promise, true ); return; } if( ref.ref === errorObj ) { this._attachExtraTrace( ref.ref.e ); this._reject(ref.ref.e); } else { thenable.addCache( x, this ); var then = ref.ref; var localX = x; var localP = this; var key = {}; var called = false; var t = function t( v ) { if( called && this !== key ) return; called = true; var fn = localP._fulfill; var b = Promise$_Cast( v ); if( b !== v || ( b instanceof Promise && b.isPending() ) ) { if( v === x ) { fn.call(localP, v); thenable.deleteCache(localX); } else { b._then( t, r, void 0, key, void 0, t); } return; } if( b instanceof Promise ) { var fn = b.isFulfilled() ? localP._fulfill : localP._reject; v = v._resolvedValue; b = Promise$_Cast( v ); if( b !== v || ( b instanceof Promise && b !== v ) ) { b._then( t, r, void 0, key, void 0, t); return; } } fn.call(localP, v); thenable.deleteCache(localX); }; var r = function r( v ) { if( called && this !== key ) return; var fn = localP._reject; called = true; var b = Promise$_Cast( v ); if( b !== v || ( b instanceof Promise && b.isPending() ) ) { if( v === x ) { fn.call(localP, v); thenable.deleteCache(localX); } else { b._then( t, r, void 0, key, void 0, t); } return; } if( b instanceof Promise ) { var fn = b.isFulfilled() ? localP._fulfill : localP._reject; v = v._resolvedValue; b = Promise$_Cast( v ); if( b !== v || ( b instanceof Promise && b.isPending() ) ) { b._then( t, r, void 0, key, void 0, t); return; } } fn.call(localP, v); thenable.deleteCache(localX); }; var threw = tryCatch2( then, x, t, r); if( threw === errorObj && !called ) { this._attachExtraTrace( threw.e ); this._reject(threw.e); thenable.deleteCache(x); } } }; Promise.prototype._tryThenable = function Promise$_tryThenable( x ) { var ref; if( !thenable.is( x, ref = {ref: null, promise: null} ) ) { return false; } this._resolveThenable( x, ref ); return true; }; Promise._cast = Promise$_Cast; };