Index: src/js/promise.js |
diff --git a/src/js/promise.js b/src/js/promise.js |
index b78f68c4f71c367365a8a149803c3091a9e55f75..25c8beaa9e0aef86dd966faa3af89d8e342c04a3 100644 |
--- a/src/js/promise.js |
+++ b/src/js/promise.js |
@@ -20,6 +20,12 @@ |
utils.ImportNow("promise_forwarding_handler_symbol"); |
var promiseHasHandlerSymbol = |
utils.ImportNow("promise_has_handler_symbol"); |
+var promiseRejectReactionsSymbol = |
+ utils.ImportNow("promise_reject_reactions_symbol"); |
+var promiseFulfillReactionsSymbol = |
+ utils.ImportNow("promise_fulfill_reactions_symbol"); |
+var promiseDeferredReactionSymbol = |
+ utils.ImportNow("promise_deferred_reaction_symbol"); |
var promiseHandledHintSymbol = |
utils.ImportNow("promise_handled_hint_symbol"); |
var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); |
@@ -30,7 +36,6 @@ |
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); |
var ObjectHasOwnProperty; |
var GlobalPromise = global.Promise; |
-var PromiseThen = GlobalPromise.prototype.then; |
utils.Import(function(from) { |
ObjectHasOwnProperty = from.ObjectHasOwnProperty; |
@@ -40,6 +45,42 @@ |
// ------------------------------------------------------------------- |
// Core functionality. |
+ |
+function PromiseSet(promise, status, value) { |
+ SET_PRIVATE(promise, promiseStateSymbol, status); |
+ SET_PRIVATE(promise, promiseResultSymbol, value); |
+ |
+ // There are 3 possible states for the resolve, reject symbols when we add |
+ // a new callback -- |
+ // 1) UNDEFINED -- This is the zero state where there is no callback |
+ // registered. When we see this state, we directly attach the callbacks to |
+ // the symbol. |
+ // 2) !IS_ARRAY -- There is a single callback directly attached to the |
+ // symbols. We need to create a new array to store additional callbacks. |
+ // 3) IS_ARRAY -- There are multiple callbacks already registered, |
+ // therefore we can just push the new callback to the existing array. |
+ SET_PRIVATE(promise, promiseFulfillReactionsSymbol, UNDEFINED); |
+ SET_PRIVATE(promise, promiseRejectReactionsSymbol, UNDEFINED); |
+ |
+ // This symbol is used only when one deferred needs to be attached. When more |
+ // than one deferred need to be attached the promise, we attach them directly |
+ // to the promiseFulfillReactionsSymbol and promiseRejectReactionsSymbol and |
+ // reset this back to UNDEFINED. |
+ SET_PRIVATE(promise, promiseDeferredReactionSymbol, UNDEFINED); |
+ |
+ return promise; |
+} |
+ |
+function PromiseCreateAndSet(status, value) { |
+ var promise = %promise_internal_constructor(); |
+ // If debug is active, notify about the newly created promise first. |
+ if (DEBUG_IS_ACTIVE) PromiseSet(promise, kPending, UNDEFINED); |
+ return PromiseSet(promise, status, value); |
+} |
+ |
+function PromiseInit(promise) { |
+ return PromiseSet(promise, kPending, UNDEFINED); |
+} |
function PromiseHandle(value, handler, deferred) { |
var debug_is_active = DEBUG_IS_ACTIVE; |
@@ -57,6 +98,7 @@ |
// Pass false for debugEvent so .then chaining or throwaway promises |
// in async functions do not trigger redundant ExceptionEvents. |
%PromiseReject(deferred.promise, exception, false); |
+ PromiseSet(deferred.promise, kRejected, exception); |
} else { |
%_Call(deferred.reject, UNDEFINED, exception); |
} |
@@ -92,6 +134,34 @@ |
return [id, name]; |
} |
+function PromiseAttachCallbacks(promise, deferred, onResolve, onReject) { |
+ var maybeResolveCallbacks = |
+ GET_PRIVATE(promise, promiseFulfillReactionsSymbol); |
+ if (IS_UNDEFINED(maybeResolveCallbacks)) { |
+ SET_PRIVATE(promise, promiseFulfillReactionsSymbol, onResolve); |
+ SET_PRIVATE(promise, promiseRejectReactionsSymbol, onReject); |
+ SET_PRIVATE(promise, promiseDeferredReactionSymbol, deferred); |
+ } else if (!IS_ARRAY(maybeResolveCallbacks)) { |
+ var resolveCallbacks = new InternalArray(); |
+ var rejectCallbacks = new InternalArray(); |
+ var existingDeferred = GET_PRIVATE(promise, promiseDeferredReactionSymbol); |
+ |
+ resolveCallbacks.push( |
+ maybeResolveCallbacks, existingDeferred, onResolve, deferred); |
+ rejectCallbacks.push(GET_PRIVATE(promise, promiseRejectReactionsSymbol), |
+ existingDeferred, |
+ onReject, |
+ deferred); |
+ |
+ SET_PRIVATE(promise, promiseFulfillReactionsSymbol, resolveCallbacks); |
+ SET_PRIVATE(promise, promiseRejectReactionsSymbol, rejectCallbacks); |
+ SET_PRIVATE(promise, promiseDeferredReactionSymbol, UNDEFINED); |
+ } else { |
+ maybeResolveCallbacks.push(onResolve, deferred); |
+ GET_PRIVATE(promise, promiseRejectReactionsSymbol).push(onReject, deferred); |
+ } |
+} |
+ |
function PromiseIdResolveHandler(x) { return x; } |
function PromiseIdRejectHandler(r) { %_ReThrow(r); } |
SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true); |
@@ -101,9 +171,8 @@ |
// For bootstrapper. |
-// This is used by utils and v8-extras. |
function PromiseCreate() { |
- return %promise_internal_constructor(); |
+ return PromiseInit(%promise_internal_constructor()); |
} |
// ES#sec-promise-resolve-functions |
@@ -112,6 +181,7 @@ |
if (resolution === promise) { |
var exception = %make_type_error(kPromiseCyclic, resolution); |
%PromiseReject(promise, exception, true); |
+ PromiseSet(promise, kRejected, exception); |
return; |
} |
if (IS_RECEIVER(resolution)) { |
@@ -120,6 +190,7 @@ |
var then = resolution.then; |
} catch (e) { |
%PromiseReject(promise, e, true); |
+ PromiseSet(promise, kRejected, e); |
return; |
} |
@@ -127,16 +198,18 @@ |
// rejected, shortcircuit the resolution procedure by directly |
// reusing the value from the promise. |
if (%is_promise(resolution) && then === PromiseThen) { |
- var thenableState = %PromiseStatus(resolution); |
+ var thenableState = GET_PRIVATE(resolution, promiseStateSymbol); |
if (thenableState === kFulfilled) { |
// This goes inside the if-else to save one symbol lookup in |
// the slow path. |
- var thenableValue = %PromiseResult(resolution); |
- %PromiseFulfill(promise, kFulfilled, thenableValue); |
+ var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); |
+ %PromiseFulfill(promise, kFulfilled, thenableValue, |
+ promiseFulfillReactionsSymbol); |
+ PromiseSet(promise, kFulfilled, thenableValue); |
SET_PRIVATE(promise, promiseHasHandlerSymbol, true); |
return; |
} else if (thenableState === kRejected) { |
- var thenableValue = %PromiseResult(resolution); |
+ var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); |
if (!HAS_DEFINED_PRIVATE(resolution, promiseHasHandlerSymbol)) { |
// Promise has already been rejected, but had no handler. |
// Revoke previously triggered reject event. |
@@ -144,6 +217,7 @@ |
} |
// Don't cause a debug event as this case is forwarding a rejection |
%PromiseReject(promise, thenableValue, false); |
+ PromiseSet(promise, kRejected, thenableValue); |
SET_PRIVATE(resolution, promiseHasHandlerSymbol, true); |
return; |
} |
@@ -158,17 +232,21 @@ |
return; |
} |
} |
- %PromiseFulfill(promise, kFulfilled, resolution); |
+ %PromiseFulfill(promise, kFulfilled, resolution, |
+ promiseFulfillReactionsSymbol); |
+ PromiseSet(promise, kFulfilled, resolution); |
} |
// Only used by async-await.js |
function RejectPromise(promise, reason, debugEvent) { |
%PromiseReject(promise, reason, debugEvent); |
+ PromiseSet(promise, kRejected, reason); |
} |
// Export to bindings |
function DoRejectPromise(promise, reason) { |
%PromiseReject(promise, reason, true); |
+ PromiseSet(promise, kRejected, reason); |
} |
// The resultCapability.promise is only ever fulfilled internally, |
@@ -176,7 +254,7 @@ |
// calling them multiple times. |
function CreateInternalPromiseCapability() { |
return { |
- promise: %promise_internal_constructor(), |
+ promise: PromiseCreate(), |
resolve: UNDEFINED, |
reject: UNDEFINED |
}; |
@@ -187,7 +265,7 @@ |
function NewPromiseCapability(C, debugEvent) { |
if (C === GlobalPromise) { |
// Optimized case, avoid extra closure. |
- var promise = %promise_internal_constructor(); |
+ var promise = PromiseCreate(); |
// TODO(gsathya): Remove container for callbacks when this is |
// moved to CPP/TF. |
var callbacks = %create_resolving_functions(promise, debugEvent); |
@@ -220,7 +298,7 @@ |
} |
if (this === GlobalPromise) { |
// Optimized case, avoid extra closure. |
- var promise = %promise_create_and_set(kRejected, r); |
+ var promise = PromiseCreateAndSet(kRejected, r); |
// Trigger debug events if the debugger is on, as Promise.reject is |
// equivalent to throwing an exception directly. |
%PromiseRejectEventFromStack(promise, r); |
@@ -232,6 +310,55 @@ |
} |
} |
+function PerformPromiseThen(promise, onResolve, onReject, resultCapability) { |
+ if (!IS_CALLABLE(onResolve)) onResolve = PromiseIdResolveHandler; |
+ if (!IS_CALLABLE(onReject)) onReject = PromiseIdRejectHandler; |
+ |
+ var status = GET_PRIVATE(promise, promiseStateSymbol); |
+ switch (status) { |
+ case kPending: |
+ PromiseAttachCallbacks(promise, resultCapability, onResolve, onReject); |
+ break; |
+ case kFulfilled: |
+ %EnqueuePromiseReactionJob(GET_PRIVATE(promise, promiseResultSymbol), |
+ onResolve, resultCapability, kFulfilled); |
+ break; |
+ case kRejected: |
+ if (!HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { |
+ // Promise has already been rejected, but had no handler. |
+ // Revoke previously triggered reject event. |
+ %PromiseRevokeReject(promise); |
+ } |
+ %EnqueuePromiseReactionJob(GET_PRIVATE(promise, promiseResultSymbol), |
+ onReject, resultCapability, kRejected); |
+ break; |
+ } |
+ |
+ // Mark this promise as having handler. |
+ SET_PRIVATE(promise, promiseHasHandlerSymbol, true); |
+ return resultCapability.promise; |
+} |
+ |
+// ES#sec-promise.prototype.then |
+// Promise.prototype.then ( onFulfilled, onRejected ) |
+// Multi-unwrapped chaining with thenable coercion. |
+function PromiseThen(onResolve, onReject) { |
+ if (!%is_promise(this)) { |
+ throw %make_type_error(kNotAPromise, this); |
+ } |
+ |
+ var constructor = SpeciesConstructor(this, GlobalPromise); |
+ var resultCapability; |
+ if (constructor === GlobalPromise) { |
+ resultCapability = CreateInternalPromiseCapability(); |
+ } else { |
+ // Pass false for debugEvent so .then chaining does not trigger |
+ // redundant ExceptionEvents. |
+ resultCapability = NewPromiseCapability(constructor, false); |
+ } |
+ return PerformPromiseThen(this, onResolve, onReject, resultCapability); |
+} |
+ |
// ES#sec-promise.prototype.catch |
// Promise.prototype.catch ( onRejected ) |
function PromiseCatch(onReject) { |
@@ -250,7 +377,7 @@ |
// Avoid creating resolving functions. |
if (this === GlobalPromise) { |
- var promise = %promise_internal_constructor(); |
+ var promise = PromiseCreate(); |
ResolvePromise(promise, x); |
return promise; |
} |
@@ -393,10 +520,8 @@ |
return true; |
} |
- if (!%is_promise(promise)) return false; |
- |
- var queue = %PromiseRejectReactions(promise); |
- var deferred = %PromiseDeferred(promise); |
+ var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol); |
+ var deferred = GET_PRIVATE(promise, promiseDeferredReactionSymbol); |
if (IS_UNDEFINED(queue)) return false; |
@@ -404,8 +529,8 @@ |
return PromiseHasUserDefinedRejectHandlerCheck(queue, deferred); |
} |
- for (var i = 0; i < queue.length; i++) { |
- if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], deferred[i])) { |
+ for (var i = 0; i < queue.length; i += 2) { |
+ if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], queue[i + 1])) { |
return true; |
} |
} |
@@ -443,6 +568,7 @@ |
utils.InstallGetter(GlobalPromise, speciesSymbol, PromiseSpecies); |
utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ |
+ "then", PromiseThen, |
"catch", PromiseCatch |
]); |
@@ -456,11 +582,7 @@ |
"promise_resolve", ResolvePromise, |
"promise_then", PromiseThen, |
"promise_handle", PromiseHandle, |
- "promise_debug_get_info", PromiseDebugGetInfo, |
- "new_promise_capability", NewPromiseCapability, |
- "internal_promise_capability", CreateInternalPromiseCapability, |
- "promise_id_resolve_handler", PromiseIdResolveHandler, |
- "promise_id_reject_handler", PromiseIdRejectHandler |
+ "promise_debug_get_info", PromiseDebugGetInfo |
]); |
// This allows extras to create promises quickly without building extra |
@@ -478,6 +600,7 @@ |
to.PromiseThen = PromiseThen; |
to.CreateInternalPromiseCapability = CreateInternalPromiseCapability; |
+ to.PerformPromiseThen = PerformPromiseThen; |
to.ResolvePromise = ResolvePromise; |
to.RejectPromise = RejectPromise; |
}); |