Skip to content

Commit 22d11af

Browse files
author
Kevin Smith
committed
[MERGE #6163 @zenparsing] Await should only get the promise's constructor property once
Merge pull request #6163 from zenparsing:promise-get-constructor Fixes #6162 Currently, `await` will trigger two gets for the operand's "constructor" property: once when doing `Promise.resolve` and then again in the call to `CreateThenPromise`. This change introduces `PerformPromiseThen`, a new function parallel to the spec's abstract operation of the same name, that takes a promise capability instead of creating a new one.
2 parents 2690e97 + a5cda3c commit 22d11af

File tree

4 files changed

+123
-46
lines changed

4 files changed

+123
-46
lines changed

lib/Runtime/Library/JavascriptPromise.cpp

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -628,39 +628,34 @@ namespace Js
628628
x = scriptContext->GetLibrary()->GetUndefined();
629629
}
630630

631-
// 3. If IsPromise(x) is true,
632-
if (VarIs<JavascriptPromise>(x))
633-
{
634-
// a. Let xConstructor be Get(x, "constructor").
635-
Var xConstructor = JavascriptOperators::GetProperty((RecyclableObject*)x, PropertyIds::constructor, scriptContext);
636-
637-
// b. If SameValue(xConstructor, C) is true, return x.
638-
if (JavascriptConversion::SameValue(xConstructor, constructor))
639-
{
640-
return x;
641-
}
642-
}
643-
644-
// 4. Let promiseCapability be NewPromiseCapability(C).
645-
// 5. Perform ? Call(promiseCapability.[[Resolve]], undefined, << x >>).
646-
// 6. Return promiseCapability.[[Promise]].
647-
return CreateResolvedPromise(x, scriptContext, constructor);
631+
return PromiseResolve(constructor, x, scriptContext);
648632
}
649633

650634
JavascriptPromise* JavascriptPromise::InternalPromiseResolve(Var value, ScriptContext* scriptContext)
651635
{
652636
Var constructor = scriptContext->GetLibrary()->GetPromiseConstructor();
637+
Var promise = PromiseResolve(constructor, value, scriptContext);
638+
return UnsafeVarTo<JavascriptPromise>(promise);
639+
}
653640

641+
Var JavascriptPromise::PromiseResolve(Var constructor, Var value, ScriptContext* scriptContext)
642+
{
654643
if (VarIs<JavascriptPromise>(value))
655644
{
656-
Var valueConstructor = JavascriptOperators::GetProperty((RecyclableObject*)value, PropertyIds::constructor, scriptContext);
645+
Var valueConstructor = JavascriptOperators::GetProperty(
646+
(RecyclableObject*)value,
647+
PropertyIds::constructor,
648+
scriptContext);
649+
650+
// If `value` is a Promise or Promise subclass instance and its "constructor"
651+
// property is `constructor`, then return the value unchanged
657652
if (JavascriptConversion::SameValue(valueConstructor, constructor))
658653
{
659-
return UnsafeVarTo<JavascriptPromise>(value);
654+
return value;
660655
}
661656
}
662657

663-
return UnsafeVarTo<JavascriptPromise>(CreateResolvedPromise(value, scriptContext, constructor));
658+
return CreateResolvedPromise(value, scriptContext, constructor);
664659
}
665660

666661
// Promise.prototype.then as described in ES 2015 Section 25.4.5.3
@@ -1255,38 +1250,59 @@ namespace Js
12551250
return NewPromiseCapability(constructor, scriptContext);
12561251
});
12571252

1258-
JavascriptPromiseReaction* resolveReaction = JavascriptPromiseReaction::New(promiseCapability, fulfillmentHandler, scriptContext);
1259-
JavascriptPromiseReaction* rejectReaction = JavascriptPromiseReaction::New(promiseCapability, rejectionHandler, scriptContext);
1253+
PerformPromiseThen(sourcePromise, promiseCapability, fulfillmentHandler, rejectionHandler, scriptContext);
1254+
1255+
return promiseCapability->GetPromise();
1256+
}
1257+
1258+
void JavascriptPromise::PerformPromiseThen(
1259+
JavascriptPromise* sourcePromise,
1260+
JavascriptPromiseCapability* capability,
1261+
RecyclableObject* fulfillmentHandler,
1262+
RecyclableObject* rejectionHandler,
1263+
ScriptContext* scriptContext)
1264+
{
1265+
auto* resolveReaction = JavascriptPromiseReaction::New(capability, fulfillmentHandler, scriptContext);
1266+
auto* rejectReaction = JavascriptPromiseReaction::New(capability, rejectionHandler, scriptContext);
12601267

12611268
switch (sourcePromise->GetStatus())
12621269
{
12631270
case PromiseStatusCode_Unresolved:
12641271
JavascriptPromiseReactionPair pair;
12651272
pair.resolveReaction = resolveReaction;
12661273
pair.rejectReaction = rejectReaction;
1267-
12681274
sourcePromise->reactions->Prepend(pair);
12691275
break;
1276+
12701277
case PromiseStatusCode_HasResolution:
1271-
EnqueuePromiseReactionTask(resolveReaction, CrossSite::MarshalVar(scriptContext, sourcePromise->result), scriptContext);
1278+
EnqueuePromiseReactionTask(
1279+
resolveReaction,
1280+
CrossSite::MarshalVar(scriptContext, sourcePromise->result),
1281+
scriptContext);
12721282
break;
1283+
12731284
case PromiseStatusCode_HasRejection:
12741285
{
12751286
if (!sourcePromise->GetIsHandled())
12761287
{
1277-
scriptContext->GetLibrary()->CallNativeHostPromiseRejectionTracker(sourcePromise, CrossSite::MarshalVar(scriptContext, sourcePromise->result), true);
1288+
scriptContext->GetLibrary()->CallNativeHostPromiseRejectionTracker(
1289+
sourcePromise,
1290+
CrossSite::MarshalVar(scriptContext, sourcePromise->result),
1291+
true);
12781292
}
1279-
EnqueuePromiseReactionTask(rejectReaction, CrossSite::MarshalVar(scriptContext, sourcePromise->result), scriptContext);
1293+
EnqueuePromiseReactionTask(
1294+
rejectReaction,
1295+
CrossSite::MarshalVar(scriptContext, sourcePromise->result),
1296+
scriptContext);
12801297
break;
12811298
}
1299+
12821300
default:
12831301
AssertMsg(false, "Promise status is in an invalid state");
12841302
break;
12851303
}
12861304

12871305
sourcePromise->SetIsHandled();
1288-
1289-
return promiseCapability->GetPromise();
12901306
}
12911307

12921308
// Promise Resolve Thenable Job as described in ES 2015 Section 25.4.2.2
@@ -1601,7 +1617,11 @@ namespace Js
16011617
return undefinedVar;
16021618
}
16031619

1604-
void JavascriptPromise::AsyncSpawnStep(JavascriptPromiseAsyncSpawnStepArgumentExecutorFunction* nextFunction, JavascriptGenerator* gen, Var resolve, Var reject)
1620+
void JavascriptPromise::AsyncSpawnStep(
1621+
JavascriptPromiseAsyncSpawnStepArgumentExecutorFunction* nextFunction,
1622+
JavascriptGenerator* gen,
1623+
Var resolve,
1624+
Var reject)
16051625
{
16061626
ScriptContext* scriptContext = gen->GetScriptContext();
16071627
BEGIN_SAFE_REENTRANT_REGION(scriptContext->GetThreadContext())
@@ -1610,13 +1630,16 @@ namespace Js
16101630
Var undefinedVar = library->GetUndefined();
16111631

16121632
JavascriptExceptionObject* exception = nullptr;
1613-
Var value = nullptr;
16141633
RecyclableObject* next = nullptr;
1615-
bool done;
16161634

16171635
try
16181636
{
1619-
Var nextVar = CALL_FUNCTION(scriptContext->GetThreadContext(), nextFunction, CallInfo(CallFlags_Value, 1), undefinedVar);
1637+
Var nextVar = CALL_FUNCTION(
1638+
scriptContext->GetThreadContext(),
1639+
nextFunction,
1640+
CallInfo(CallFlags_Value, 1),
1641+
undefinedVar);
1642+
16201643
next = VarTo<RecyclableObject>(nextVar);
16211644
}
16221645
catch (const JavascriptException& err)
@@ -1626,35 +1649,54 @@ namespace Js
16261649

16271650
if (exception != nullptr)
16281651
{
1629-
// finished with failure, reject the promise
1652+
// If the generator threw an exception, reject the promise
16301653
TryRejectWithExceptionObject(exception, reject, scriptContext);
16311654
return;
16321655
}
16331656

16341657
Assert(next != nullptr);
1635-
done = JavascriptConversion::ToBool(JavascriptOperators::GetProperty(next, PropertyIds::done, scriptContext), scriptContext);
1636-
if (done)
1658+
1659+
Var done = JavascriptOperators::GetProperty(next, PropertyIds::done, scriptContext);
1660+
1661+
if (JavascriptConversion::ToBool(done, scriptContext))
16371662
{
1638-
// finished with success, resolve the promise
1639-
value = JavascriptOperators::GetProperty(next, PropertyIds::value, scriptContext);
1663+
// If the generator is done, resolve the promise
1664+
Var value = JavascriptOperators::GetProperty(next, PropertyIds::value, scriptContext);
16401665
if (!JavascriptConversion::IsCallable(resolve))
16411666
{
16421667
JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction);
16431668
}
1644-
CALL_FUNCTION(scriptContext->GetThreadContext(), VarTo<RecyclableObject>(resolve), CallInfo(CallFlags_Value, 2), undefinedVar, value);
1669+
1670+
CALL_FUNCTION(
1671+
scriptContext->GetThreadContext(),
1672+
VarTo<RecyclableObject>(resolve),
1673+
CallInfo(CallFlags_Value, 2),
1674+
undefinedVar,
1675+
value);
16451676

16461677
return;
16471678
}
16481679

1649-
// not finished, chain off the yielded promise and `step` again
1650-
JavascriptPromiseAsyncSpawnStepArgumentExecutorFunction* successFunction = library->CreatePromiseAsyncSpawnStepArgumentExecutorFunction(EntryJavascriptPromiseAsyncSpawnCallStepExecutorFunction, gen, undefinedVar, resolve, reject);
1651-
JavascriptPromiseAsyncSpawnStepArgumentExecutorFunction* failFunction = library->CreatePromiseAsyncSpawnStepArgumentExecutorFunction(EntryJavascriptPromiseAsyncSpawnCallStepExecutorFunction, gen, undefinedVar, resolve, reject, true);
1680+
// Chain off the yielded promise and step again
1681+
auto* successFunction = library->CreatePromiseAsyncSpawnStepArgumentExecutorFunction(
1682+
EntryJavascriptPromiseAsyncSpawnCallStepExecutorFunction,
1683+
gen,
1684+
undefinedVar,
1685+
resolve,
1686+
reject);
1687+
1688+
auto* failFunction = library->CreatePromiseAsyncSpawnStepArgumentExecutorFunction(
1689+
EntryJavascriptPromiseAsyncSpawnCallStepExecutorFunction,
1690+
gen,
1691+
undefinedVar,
1692+
resolve,
1693+
reject,
1694+
true);
16521695

1653-
JavascriptFunction* promiseResolve = library->EnsurePromiseResolveFunction();
1654-
value = JavascriptOperators::GetProperty(next, PropertyIds::value, scriptContext);
1655-
Var promiseVar = CALL_FUNCTION(scriptContext->GetThreadContext(), promiseResolve, CallInfo(CallFlags_Value, 2), library->GetPromiseConstructor(), value);
1656-
JavascriptPromise* promise = VarTo<JavascriptPromise>(promiseVar);
1657-
CreateThenPromise(promise, successFunction, failFunction, scriptContext);
1696+
Var value = JavascriptOperators::GetProperty(next, PropertyIds::value, scriptContext);
1697+
JavascriptPromise* promise = InternalPromiseResolve(value, scriptContext);
1698+
JavascriptPromiseCapability* unused = UnusedPromiseCapability(scriptContext);
1699+
PerformPromiseThen(promise, unused, successFunction, failFunction, scriptContext);
16581700

16591701
END_SAFE_REENTRANT_REGION
16601702
}
@@ -1798,6 +1840,12 @@ namespace Js
17981840
END_SAFE_REENTRANT_CALL
17991841
}
18001842

1843+
JavascriptPromiseCapability* JavascriptPromise::UnusedPromiseCapability(ScriptContext* scriptContext)
1844+
{
1845+
// TODO(zenparsing): Optimize me
1846+
return NewPromiseCapability(scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext);
1847+
}
1848+
18011849
// CreatePromiseCapabilityRecord as described in ES6.0 (draft 29) Section 25.4.1.6.1
18021850
JavascriptPromiseCapability* JavascriptPromise::CreatePromiseCapabilityRecord(RecyclableObject* constructor, ScriptContext* scriptContext)
18031851
{

lib/Runtime/Library/JavascriptPromise.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,16 @@ namespace Js
471471
static Var CreateResolvedPromise(Var resolution, ScriptContext* scriptContext, Var promiseConstructor = nullptr);
472472
static Var CreatePassThroughPromise(JavascriptPromise* sourcePromise, ScriptContext* scriptContext);
473473
static Var CreateThenPromise(JavascriptPromise* sourcePromise, RecyclableObject* fulfillmentHandler, RecyclableObject* rejectionHandler, ScriptContext* scriptContext);
474+
474475
static JavascriptPromise* InternalPromiseResolve(Var value, ScriptContext* scriptContext);
476+
static Var PromiseResolve(Var constructor, Var value, ScriptContext* scriptContext);
477+
478+
static void PerformPromiseThen(
479+
JavascriptPromise* sourcePromise,
480+
JavascriptPromiseCapability* capability,
481+
RecyclableObject* fulfillmentHandler,
482+
RecyclableObject* rejectionHandler,
483+
ScriptContext* scriptContext);
475484

476485
virtual BOOL GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
477486
virtual BOOL GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
@@ -480,6 +489,7 @@ namespace Js
480489

481490

482491
static JavascriptPromiseCapability* NewPromiseCapability(Var constructor, ScriptContext* scriptContext);
492+
static JavascriptPromiseCapability* UnusedPromiseCapability(ScriptContext* scriptContext);
483493
static JavascriptPromiseCapability* CreatePromiseCapabilityRecord(RecyclableObject* constructor, ScriptContext* scriptContext);
484494
static Var TriggerPromiseReactions(JavascriptPromiseReactionList* reactions, bool isReject, Var resolution, ScriptContext* scriptContext);
485495
static void EnqueuePromiseReactionTask(JavascriptPromiseReaction* reaction, Var resolution, ScriptContext* scriptContext);

test/es7/asyncawait-functionality.baseline

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ Test #32 - Success initial value of the body symbol is the same as the default p
4040
Executing test #33 - `then` is not called when awaiting a non-promise or native promise
4141
Executing test #34 - `then` is called when awaiting a promise subclass
4242
Executing test #35 - `then` is called when awaiting a non-promise thenable
43+
Executing test #36 - The constructor property is only accessed once by await
44+
Test #36 - constructor property accessed
4345

4446
Completion Results:
4547
Test #1 - Success lambda expression with no argument called with result = 'true'

test/es7/asyncawait-functionality.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,23 @@ var tests = [
10891089
await thenable;
10901090
}
10911091

1092+
f();
1093+
}
1094+
},
1095+
{
1096+
name: "The constructor property is only accessed once by await",
1097+
body: function (index) {
1098+
async function f() {
1099+
let p = Promise.resolve(0);
1100+
Object.defineProperty(p, 'constructor', {
1101+
get: function() {
1102+
echo(`Test #${index} - constructor property accessed`);
1103+
return Promise;
1104+
}
1105+
});
1106+
await p;
1107+
}
1108+
10921109
f();
10931110
}
10941111
}

0 commit comments

Comments
 (0)