Index: src/js/promise.js |
diff --git a/src/js/promise.js b/src/js/promise.js |
index b50fc80b305bdcfd509e479cfb703171b85eac9b..9e4179ae30d7013bbbc16a58cca7acb2dc0c2584 100644 |
--- a/src/js/promise.js |
+++ b/src/js/promise.js |
@@ -44,7 +44,7 @@ var lastMicrotaskId = 0; |
// ES#sec-createresolvingfunctions |
// CreateResolvingFunctions ( promise ) |
-function CreateResolvingFunctions(promise) { |
+function CreateResolvingFunctions(promise, debugEvent) { |
var alreadyResolved = false; |
// ES#sec-promise-resolve-functions |
@@ -60,7 +60,7 @@ function CreateResolvingFunctions(promise) { |
var reject = reason => { |
if (alreadyResolved === true) return; |
alreadyResolved = true; |
- RejectPromise(promise, reason); |
+ RejectPromise(promise, reason, debugEvent); |
}; |
return { |
@@ -83,7 +83,8 @@ var GlobalPromise = function Promise(executor) { |
} |
var promise = PromiseInit(%_NewObject(GlobalPromise, new.target)); |
- var callbacks = CreateResolvingFunctions(promise); |
+ // Calling the reject function would be a new exception, so debugEvent = true |
+ var callbacks = CreateResolvingFunctions(promise, true); |
var debug_is_active = DEBUG_IS_ACTIVE; |
try { |
if (debug_is_active) %DebugPushPromise(promise); |
@@ -238,14 +239,16 @@ function PromiseCreate() { |
// Promise Resolve Functions, steps 6-13 |
function ResolvePromise(promise, resolution) { |
if (resolution === promise) { |
- return RejectPromise(promise, %make_type_error(kPromiseCyclic, resolution)); |
+ return RejectPromise(promise, |
+ %make_type_error(kPromiseCyclic, resolution), |
+ true); |
} |
if (IS_RECEIVER(resolution)) { |
// 25.4.1.3.2 steps 8-12 |
try { |
var then = resolution.then; |
} catch (e) { |
- return RejectPromise(promise, e); |
+ return RejectPromise(promise, e, true); |
} |
// Resolution is a native promise and if it's already resolved or |
@@ -268,7 +271,8 @@ function ResolvePromise(promise, resolution) { |
// Revoke previously triggered reject event. |
%PromiseRevokeReject(resolution); |
} |
- RejectPromise(promise, thenableValue); |
+ // Don't cause a debug event as this case is forwarding a rejection |
+ RejectPromise(promise, thenableValue, false); |
SET_PRIVATE(resolution, promiseHasHandlerSymbol, true); |
return; |
} |
@@ -283,7 +287,9 @@ function ResolvePromise(promise, resolution) { |
if (instrumenting) { |
%DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); |
} |
- var callbacks = CreateResolvingFunctions(promise); |
+ // These resolving functions simply forward the exception, so |
+ // don't create a new debugEvent. |
+ var callbacks = CreateResolvingFunctions(promise, false); |
try { |
%_Call(then, resolution, callbacks.resolve, callbacks.reject); |
} catch (e) { |
@@ -305,26 +311,35 @@ function ResolvePromise(promise, resolution) { |
// ES#sec-rejectpromise |
// RejectPromise ( promise, reason ) |
-function RejectPromise(promise, reason) { |
+function RejectPromise(promise, reason, debugEvent) { |
// Check promise status to confirm that this reject has an effect. |
// Call runtime for callbacks to the debugger or for unhandled reject. |
+ // The debugEvent parameter sets whether a debug ExceptionEvent should |
+ // be triggered. It should be set to false when forwarding a rejection |
+ // rather than creating a new one. |
if (GET_PRIVATE(promise, promiseStateSymbol) === kPending) { |
- var debug_is_active = DEBUG_IS_ACTIVE; |
- if (debug_is_active || |
+ // This check is redundant with checks in the runtime, but it may help |
+ // avoid unnecessary runtime calls. |
+ if ((debugEvent && DEBUG_IS_ACTIVE) || |
!HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { |
- %PromiseRejectEvent(promise, reason, debug_is_active); |
+ %PromiseRejectEvent(promise, reason, debugEvent); |
} |
} |
FulfillPromise(promise, kRejected, reason, promiseRejectReactionsSymbol) |
} |
+// Export to bindings |
+function DoRejectPromise(promise, reason) { |
+ return RejectPromise(promise, reason, true); |
+} |
+ |
// ES#sec-newpromisecapability |
// NewPromiseCapability ( C ) |
-function NewPromiseCapability(C) { |
+function NewPromiseCapability(C, debugEvent) { |
if (C === GlobalPromise) { |
// Optimized case, avoid extra closure. |
var promise = PromiseInit(new GlobalPromise(promiseRawSymbol)); |
- var callbacks = CreateResolvingFunctions(promise); |
+ var callbacks = CreateResolvingFunctions(promise, debugEvent); |
return { |
promise: promise, |
resolve: callbacks.resolve, |
@@ -355,12 +370,12 @@ function PromiseReject(r) { |
if (this === GlobalPromise) { |
// Optimized case, avoid extra closure. |
var promise = PromiseCreateAndSet(kRejected, r); |
- // The debug event for this would always be an uncaught promise reject, |
- // which is usually simply noise. Do not trigger that debug event. |
- %PromiseRejectEvent(promise, r, false); |
+ // Trigger debug events if the debugger is on, as Promise.reject is |
+ // equivalent to throwing an exception directly. |
+ %PromiseRejectEventFromStack(promise, r); |
return promise; |
} else { |
- var promiseCapability = NewPromiseCapability(this); |
+ var promiseCapability = NewPromiseCapability(this, true); |
%_Call(promiseCapability.reject, UNDEFINED, r); |
return promiseCapability.promise; |
} |
@@ -369,7 +384,11 @@ function PromiseReject(r) { |
// Shortcut Promise.reject and Promise.resolve() implementations, used by |
// Async Functions implementation. |
function PromiseCreateRejected(r) { |
- return %_Call(PromiseReject, GlobalPromise, r); |
+ var promise = PromiseCreateAndSet(kRejected, r); |
+ // This is called from the desugaring of async/await; no reason to |
+ // create a redundant reject event. |
+ %PromiseRejectEvent(promise, r, false); |
+ return promise; |
} |
function PromiseCreateResolved(value) { |
@@ -427,7 +446,9 @@ function PromiseThen(onResolve, onReject) { |
} |
var constructor = SpeciesConstructor(this, GlobalPromise); |
- var resultCapability = NewPromiseCapability(constructor); |
+ // Pass false for debugEvent so .then chaining does not trigger |
+ // redundant ExceptionEvents. |
+ var resultCapability = NewPromiseCapability(constructor, false); |
return PerformPromiseThen(this, onResolve, onReject, resultCapability); |
} |
@@ -454,7 +475,8 @@ function PromiseResolve(x) { |
return promise; |
} |
- var promiseCapability = NewPromiseCapability(this); |
+ // debugEvent is not so meaningful here as it will be resolved |
+ var promiseCapability = NewPromiseCapability(this, true); |
var resolveResult = %_Call(promiseCapability.resolve, UNDEFINED, x); |
return promiseCapability.promise; |
} |
@@ -466,7 +488,9 @@ function PromiseAll(iterable) { |
throw %make_type_error(kCalledOnNonObject, "Promise.all"); |
} |
- var deferred = NewPromiseCapability(this); |
+ // false debugEvent so that forwarding the rejection through all does not |
+ // trigger redundant ExceptionEvents |
+ var deferred = NewPromiseCapability(this, false); |
var resolutions = new InternalArray(); |
var count; |
@@ -517,7 +541,9 @@ function PromiseRace(iterable) { |
throw %make_type_error(kCalledOnNonObject, PromiseRace); |
} |
- var deferred = NewPromiseCapability(this); |
+ // false debugEvent so that forwarding the rejection through race does not |
+ // trigger redundant ExceptionEvents |
+ var deferred = NewPromiseCapability(this, false); |
try { |
for (var value of iterable) { |
this.resolve(value).then(deferred.resolve, deferred.reject); |
@@ -598,7 +624,7 @@ utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ |
"promise_catch", PromiseCatch, |
"promise_create", PromiseCreate, |
"promise_has_user_defined_reject_handler", PromiseHasUserDefinedRejectHandler, |
- "promise_reject", RejectPromise, |
+ "promise_reject", DoRejectPromise, |
"promise_resolve", ResolvePromise, |
"promise_then", PromiseThen, |
"promise_create_rejected", PromiseCreateRejected, |
@@ -611,7 +637,7 @@ utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ |
utils.InstallFunctions(extrasUtils, 0, [ |
"createPromise", PromiseCreate, |
"resolvePromise", ResolvePromise, |
- "rejectPromise", RejectPromise |
+ "rejectPromise", DoRejectPromise |
]); |
utils.Export(function(to) { |