Skip to content

Commit

Permalink
Use a free list to reuse PromiseCapability objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
cscott committed Dec 12, 2015
1 parent 44d3b8b commit 9f27ecd
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 101 deletions.
1 change: 1 addition & 0 deletions lib/promise-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};
};
Expand Down
142 changes: 41 additions & 101 deletions lib/promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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');
}
Expand All @@ -190,55 +170,26 @@ 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.
handlerResult = tryCatch1(handler, 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() {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
},
});

Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 9f27ecd

Please sign in to comment.