diff --git a/lib/promise-extra.js b/lib/promise-extra.js index c3e0a61..c4c058c 100644 --- a/lib/promise-extra.js +++ b/lib/promise-extra.js @@ -245,6 +245,7 @@ module.exports = function( if ((--remaining.count) === 0) { var resolve = capability.resolve; resolve(values); // Call w/ this===undefined + capability.free(); // Return to pool } }; }; diff --git a/lib/promise.js b/lib/promise.js index 8044caa..72d2bf4 100644 --- a/lib/promise.js +++ b/lib/promise.js @@ -8,6 +8,8 @@ var supportSpecies = false; // When this is false we assume promise subclass constructors have no // side effects. var strictConstructors = false; +// When this is true, a new resolver is created for every Promise. +var dontReuseResolvers = false; var _forEach = Function.call.bind(Array.prototype.forEach); var _toString = Function.call.bind(Object.prototype.toString); @@ -54,15 +56,6 @@ function tryCatch2r(fn, receiver, arg1, arg2) { return errorObj; } } -// The -n variant creates a new object. -function tryCatch1n(Fn, arg1) { - try { - return new Fn(arg1); - } catch (e) { - errorObj.e = e; - return errorObj; - } -} // Promises // Simplest possible implementation, but stealing tricks from Bluebird @@ -120,63 +113,50 @@ function IsPromise(promise) { return true; } -var fastCapability = null; -function fastResolver(resolve, reject) { - if (fastCapability !== true) { - throw new TypeError('Bad Promise implementation!'); - } - if (resolve === void 0 && reject === void 0) { - return; // Weird corner case in the spec. - } - fastCapability = new PromiseCapability(resolve, reject); -} - // "PromiseCapability" in the spec is what most promise implementations // call a "deferred". // We're going to wrap it so that it never throws an exception. -function PromiseCapability(resolve, reject) { +function PromiseCapability() { // Declare fields of this object. // (Helps with object shape optimization.) + var self = this; this.promise = void 0; - this.resolve = resolve; - this.reject = reject; + this.resolve = void 0; + this.reject = void 0; + this.resolver = function(resolve, reject) { + if (self.resolve !== void 0 || self.reject !== void 0) { + throw new TypeError('Bad Promise implementation!'); + } + self.resolve = resolve; + self.reject = reject; + }; } +PromiseCapability.prototype.free = function() { + if (dontReuseResolvers) { return; } + this.promise = void 0; + this.resolve = void 0; + this.reject = void 0; + freeCapabilityList.push(this); +}; +PromiseCapability.prototype.getPromiseAndFree = function() { + var promise = this.promise; + this.free(); + return promise; +}; +var freeCapabilityList = []; function makeCapability(C) { if (!IsConstructor(C)) { - thenFastPath = FAST_PATH_NONE; throw new TypeError('Bad promise constructor'); } - var capability = void 0; - var promise; - if (fastCapability === null) { - // Fast path: avoid creating a new resolver. - fastCapability = true; - promise = tryCatch1n(C, fastResolver); - capability = fastCapability; - fastCapability = null; - var wasFastPath = (thenFastPath === FAST_PATH_CTOR_BAILED); - thenFastPath = FAST_PATH_NONE; - if (promise === errorObj) { - throw errorObj.e; - } - if (wasFastPath) { return promise; } + var capability; + if (freeCapabilityList.length) { + capability = freeCapabilityList.pop(); } else { - // Normal, slow path. - thenFastPath = FAST_PATH_NONE; - var resolver = function slowCapabilityResolver(resolve, reject) { - if (capability !== void 0) { - throw new TypeError('Bad Promise implementation!'); - } - if (resolve === void 0 && reject === void 0) { - return; // Weird corner case in the spec. - } - capability = new PromiseCapability(resolve, reject); - }; - promise = new C(resolver); + capability = new PromiseCapability(); } - capability.promise = promise; + capability.promise = new C(capability.resolver); if (!(IsCallable(capability.resolve) && IsCallable(capability.reject))) { throw new TypeError('Bad promise constructor'); } @@ -190,45 +170,12 @@ var fakeRetvalFromThen = false; // Constants for Promise implementation var PROMISE_IDENTITY = (function PROMISE_IDENTITY(v) { return v; }); var PROMISE_THROWER = (function PROMISE_THROWER(t) { throw t; }); -var PROMISE_FAKE_CAPABILITY = new PromiseCapability(void 0, void 0); +var PROMISE_FAKE_CAPABILITY = new PromiseCapability(); var PROMISE_PENDING = 0; var PROMISE_RESOLVING = 1; // PROMISE_PENDING combined with alreadyResolved var PROMISE_FULFILLED = 2; var PROMISE_REJECTED = 3; -// States for then-fast-path -var FAST_PATH_NONE = 0; -var FAST_PATH_MAKE_CAP = 1; -var FAST_PATH_CTOR_BAILED = 2; - -var thenFastPath = FAST_PATH_NONE; - -function promiseReactionResolve(promiseCapability, handlerResult) { - if (promiseCapability.constructor === PromiseCapability) { - var resolve = promiseCapability.resolve; - resolve(handlerResult); - } else { - // Optimized case; this is a "standard" promise. - /* jshint bitwise: false */ - var promise = promiseCapability; - if ((promise._promise_state & 3) !== PROMISE_PENDING) { return; } - promise._promise_state++; // Sets state to PROMISE_RESOLVING - resolvePromise(promise, handlerResult); - } -} -function promiseReactionReject(promiseCapability, handlerResult) { - if (promiseCapability.constructor === PromiseCapability) { - var reject = promiseCapability.reject; - reject(handlerResult); - } else { - // Optimized case; this is a "standard" promise. - /* jshint bitwise: false */ - var promise = promiseCapability; - if ((promise._promise_state & 3) !== PROMISE_PENDING) { return; } - promise._promise_state++; // Sets state to PROMISE_RESOLVING - rejectPromise(promise, handlerResult); - } -} function promiseReactionJob(handler, promiseCapability, argument) { var handlerResult; // Encapsulate try/catch here to avoid deoptimization. @@ -236,9 +183,13 @@ function promiseReactionJob(handler, promiseCapability, argument) { if (promiseCapability === PROMISE_FAKE_CAPABILITY) { return; } if (handlerResult === errorObj) { handlerResult = handlerResult.e; - return promiseReactionReject(promiseCapability, handlerResult); + var reject = promiseCapability.reject; + reject(handlerResult); + } else { + var resolve = promiseCapability.resolve; + resolve(handlerResult); } - return promiseReactionResolve(promiseCapability, handlerResult); + promiseCapability.free(); } function PromiseReactionJobTask() { @@ -401,11 +352,6 @@ function Promise(resolver) { this._promise_fulfillReactions0 = void 0; this._promise_rejectReactions0 = void 0; this._promise_reactionCapability0 = void 0; - if (thenFastPath === FAST_PATH_MAKE_CAP && resolver === fastResolver) { - // We will create the resolving functions lazily. - thenFastPath = FAST_PATH_CTOR_BAILED; - return; - } // see https://bugs.ecmascript.org/show_bug.cgi?id=2482 // (This check has been reordered after the fast path.) if (!IsCallable(resolver)) { @@ -442,7 +388,7 @@ defineProperties(Promise, { var capability = makeCapability(C); var rejectFunc = capability.reject; rejectFunc(reason); // Call with this===undefined - return capability.promise; + return capability.getPromiseAndFree(); }, resolve: function resolve(v) { @@ -458,7 +404,7 @@ defineProperties(Promise, { var capability = makeCapability(C); var resolveFunc = capability.resolve; resolveFunc(v); // Call with this===undefined - return capability.promise; + return capability.getPromiseAndFree(); }, }); @@ -483,10 +429,7 @@ defineProperties(Promise$prototype, { (C === Promise || C.hasOwnProperty('noSideEffects'))) { resultCapability = PROMISE_FAKE_CAPABILITY; } else { - // We might create a fake capability here. - thenFastPath = FAST_PATH_MAKE_CAP; resultCapability = makeCapability(C); - // `thenFastPath` is always reset to FAST_PATH_NONE by makeCapability } } // PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability) @@ -525,10 +468,7 @@ defineProperties(Promise$prototype, { default: throw new TypeError('unexpected'); } - if (resultCapability.constructor === PromiseCapability) { - return resultCapability.promise; - } - return resultCapability; + return resultCapability.promise; }, }); // Store the identify of the Promise#then function for optimization.