Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"Namespace": "Jint.Tests.Test262",
"Parallel": true,
"ExcludedFeatures": [
"await-dictionary",
"import-bytes",
"import-defer",
"regexp-lookbehind",
Expand Down
199 changes: 198 additions & 1 deletion Jint/Native/Promise/PromiseConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ protected override void Initialize()
{
const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
const PropertyFlag LengthFlags = PropertyFlag.Configurable;
var properties = new PropertyDictionary(8, checkExistingKeys: false)
var properties = new PropertyDictionary(10, checkExistingKeys: false)
{
["all"] = new(new PropertyDescriptor(new ClrFunction(Engine, "all", All, 1, LengthFlags), PropertyFlags)),
["allKeyed"] = new(new PropertyDescriptor(new ClrFunction(Engine, "allKeyed", AllKeyed, 1, LengthFlags), PropertyFlags)),
["allSettled"] = new(new PropertyDescriptor(new ClrFunction(Engine, "allSettled", AllSettled, 1, LengthFlags), PropertyFlags)),
["allSettledKeyed"] = new(new PropertyDescriptor(new ClrFunction(Engine, "allSettledKeyed", AllSettledKeyed, 1, LengthFlags), PropertyFlags)),
["any"] = new(new PropertyDescriptor(new ClrFunction(Engine, "any", Any, 1, LengthFlags), PropertyFlags)),
["race"] = new(new PropertyDescriptor(new ClrFunction(Engine, "race", Race, 1, LengthFlags), PropertyFlags)),
["reject"] = new(new PropertyDescriptor(new ClrFunction(Engine, "reject", Reject, 1, LengthFlags), PropertyFlags)),
Expand Down Expand Up @@ -207,6 +209,201 @@ private JsValue Try(JsValue thisObject, JsCallArguments arguments)
return promiseCapability.PromiseInstance;
}

// https://tc39.es/proposal-await-dictionary/#sec-promise.allkeyed
private JsValue AllKeyed(JsValue thisObject, JsCallArguments arguments)
{
if (!thisObject.IsObject())
{
Throw.TypeError(_realm, "Promise.allKeyed called on non-object");
}

var capability = NewPromiseCapability(_engine, thisObject);

ICallable promiseResolve;
try
{
promiseResolve = GetPromiseResolve(thisObject);
}
catch (JavaScriptException e)
{
capability.Reject.Call(Undefined, e.Error);
return capability.PromiseInstance;
}

try
{
var promises = arguments.At(0);
if (!promises.IsObject())
{
Throw.TypeError(_realm, "Promise.allKeyed requires an object argument");
}

PerformPromiseAllKeyed(allSettled: false, (ObjectInstance) promises, thisObject, capability, promiseResolve);
}
catch (JavaScriptException e)
{
capability.Reject.Call(Undefined, e.Error);
}

return capability.PromiseInstance;
}

// https://tc39.es/proposal-await-dictionary/#sec-promise.allsettledkeyed
private JsValue AllSettledKeyed(JsValue thisObject, JsCallArguments arguments)
{
if (!thisObject.IsObject())
{
Throw.TypeError(_realm, "Promise.allSettledKeyed called on non-object");
}

var capability = NewPromiseCapability(_engine, thisObject);

ICallable promiseResolve;
try
{
promiseResolve = GetPromiseResolve(thisObject);
}
catch (JavaScriptException e)
{
capability.Reject.Call(Undefined, e.Error);
return capability.PromiseInstance;
}

try
{
var promises = arguments.At(0);
if (!promises.IsObject())
{
Throw.TypeError(_realm, "Promise.allSettledKeyed requires an object argument");
}

PerformPromiseAllKeyed(allSettled: true, (ObjectInstance) promises, thisObject, capability, promiseResolve);
}
catch (JavaScriptException e)
{
capability.Reject.Call(Undefined, e.Error);
}

return capability.PromiseInstance;
}

// https://tc39.es/proposal-await-dictionary/#sec-performpromiseallkeyed
private void PerformPromiseAllKeyed(
bool allSettled,
ObjectInstance promises,
JsValue constructor,
PromiseCapability resultCapability,
ICallable promiseResolve)
{
var allKeys = promises.GetOwnPropertyKeys();
var keys = new List<JsValue>();
var values = new List<JsValue>();
var remainingElementsCount = 1;
var index = 0;

foreach (var key in allKeys)
{
var desc = promises.GetOwnProperty(key);
if (desc == PropertyDescriptor.Undefined || !desc.Enumerable)
{
continue;
}

keys.Add(key);
values.Add(null!);

var value = promises.Get(key);
var nextPromise = promiseResolve.Call(constructor, value);

var capturedIndex = index;
var alreadyCalled = false;

if (allSettled)
{
var onFulfilled = new ClrFunction(_engine, "", (_, args) =>
{
if (!alreadyCalled)
{
alreadyCalled = true;
var res = _engine.Realm.Intrinsics.Object.Construct(2);
res.FastSetDataProperty("status", "fulfilled");
res.FastSetDataProperty("value", args.At(0));
values[capturedIndex] = res;
remainingElementsCount--;
if (remainingElementsCount == 0)
{
var resultObj = CreateKeyedPromiseCombinatorResultObject(keys, values);
resultCapability.Resolve.Call(Undefined, resultObj);
}
}
return Undefined;
}, 1, PropertyFlag.Configurable);

var onRejected = new ClrFunction(_engine, "", (_, args) =>
{
if (!alreadyCalled)
{
alreadyCalled = true;
var res = _engine.Realm.Intrinsics.Object.Construct(2);
res.FastSetDataProperty("status", "rejected");
res.FastSetDataProperty("reason", args.At(0));
values[capturedIndex] = res;
remainingElementsCount--;
if (remainingElementsCount == 0)
{
var resultObj = CreateKeyedPromiseCombinatorResultObject(keys, values);
resultCapability.Resolve.Call(Undefined, resultObj);
}
}
return Undefined;
}, 1, PropertyFlag.Configurable);

remainingElementsCount++;
_engine.Invoke(nextPromise, "then", [onFulfilled, onRejected]);
}
else
{
var onFulfilled = new ClrFunction(_engine, "", (_, args) =>
{
if (!alreadyCalled)
{
alreadyCalled = true;
values[capturedIndex] = args.At(0);
remainingElementsCount--;
if (remainingElementsCount == 0)
{
var resultObj = CreateKeyedPromiseCombinatorResultObject(keys, values);
resultCapability.Resolve.Call(Undefined, resultObj);
}
}
return Undefined;
}, 1, PropertyFlag.Configurable);

remainingElementsCount++;
_engine.Invoke(nextPromise, "then", [(JsValue) onFulfilled, resultCapability.RejectObj]);
}

index++;
}

remainingElementsCount--;
if (remainingElementsCount == 0)
{
var resultObj = CreateKeyedPromiseCombinatorResultObject(keys, values);
resultCapability.Resolve.Call(Undefined, resultObj);
}
}

private JsObject CreateKeyedPromiseCombinatorResultObject(List<JsValue> keys, List<JsValue> values)
{
var obj = OrdinaryObjectCreate(_engine, null);
for (var i = 0; i < keys.Count; i++)
{
obj.CreateDataPropertyOrThrow(keys[i], values[i]);
}
return obj;
}

// This helper methods executes the first 6 steps in the specs belonging to static Promise methods like all, any etc.
// If it returns false, that means it has an error and it is already rejected
// If it returns true, the logic specific to the calling function should continue executing
Expand Down