Skip to content

Commit

Permalink
Reimplement Promise#then fast path.
Browse files Browse the repository at this point in the history
  • Loading branch information
cscott committed Dec 12, 2015
1 parent 9f27ecd commit b6eea14
Showing 1 changed file with 75 additions and 8 deletions.
83 changes: 75 additions & 8 deletions lib/promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ 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 @@ -147,6 +156,7 @@ var freeCapabilityList = [];

function makeCapability(C) {
if (!IsConstructor(C)) {
thenFastPath = FAST_PATH_NONE;
throw new TypeError('Bad promise constructor');
}

Expand All @@ -156,7 +166,20 @@ function makeCapability(C) {
} else {
capability = new PromiseCapability();
}
capability.promise = new C(capability.resolver);
if (thenFastPath === FAST_PATH_MAKE_CAP) {
thenFastPathResolver = capability.resolver;
capability.promise = tryCatch1n(C, thenFastPathResolver);
var wasFastPath = (thenFastPath === FAST_PATH_CTOR_BAILED);
thenFastPathResolver = null;
thenFastPath = FAST_PATH_NONE;
if (capability.promise === errorObj) {
capability.free();
throw errorObj.e;
}
if (wasFastPath) { return capability.getPromiseAndFree(); }
} else {
capability.promise = new C(capability.resolver);
}
if (!(IsCallable(capability.resolve) && IsCallable(capability.reject))) {
throw new TypeError('Bad promise constructor');
}
Expand All @@ -176,20 +199,52 @@ 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;
var thenFastPathResolver = null;

function promiseReactionResolve(promiseCapability, handlerResult) {
if (promiseCapability.constructor === PromiseCapability) {
var resolve = promiseCapability.resolve;
resolve(handlerResult);
promiseCapability.free();
} 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);
promiseCapability.free();
} 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;
var reject = promiseCapability.reject;
reject(handlerResult);
} else {
var resolve = promiseCapability.resolve;
resolve(handlerResult);
return promiseReactionReject(promiseCapability, handlerResult);
}
promiseCapability.free();
return promiseReactionResolve(promiseCapability, handlerResult);
}

function PromiseReactionJobTask() {
Expand Down Expand Up @@ -352,6 +407,12 @@ 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 === thenFastPathResolver) {
// 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 @@ -429,7 +490,10 @@ 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 @@ -468,7 +532,10 @@ defineProperties(Promise$prototype, {
default:
throw new TypeError('unexpected');
}
return resultCapability.promise;
if (resultCapability.constructor === PromiseCapability) {
return resultCapability.promise;
}
return resultCapability;
},
});
// Store the identify of the Promise#then function for optimization.
Expand Down

0 comments on commit b6eea14

Please sign in to comment.