|
@@ -40,28 +40,21 @@ namespace Jint.Native.Promise
|
|
|
{
|
|
|
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
|
|
|
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
|
|
|
- var properties = new PropertyDictionary(5, checkExistingKeys: false)
|
|
|
- {
|
|
|
- ["resolve"] =
|
|
|
- new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "resolve", Resolve, 1, lengthFlags),
|
|
|
- propertyFlags)),
|
|
|
- ["reject"] =
|
|
|
- new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "reject", Reject, 1, lengthFlags),
|
|
|
- propertyFlags)),
|
|
|
- ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, lengthFlags),
|
|
|
- propertyFlags)),
|
|
|
- ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, lengthFlags),
|
|
|
- propertyFlags)),
|
|
|
- ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, lengthFlags),
|
|
|
- propertyFlags)),
|
|
|
+ var properties = new PropertyDictionary(6, checkExistingKeys: false)
|
|
|
+ {
|
|
|
+ ["resolve"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "resolve", Resolve, 1, lengthFlags), propertyFlags)),
|
|
|
+ ["reject"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "reject", Reject, 1, lengthFlags), propertyFlags)),
|
|
|
+ ["all"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "all", All, 1, lengthFlags), propertyFlags)),
|
|
|
+ ["allSettled"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "allSettled", AllSettled, 1, lengthFlags), propertyFlags)),
|
|
|
+ ["any"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "any", Any, 1, lengthFlags), propertyFlags)),
|
|
|
+ ["race"] = new(new PropertyDescriptor(new ClrFunctionInstance(Engine, "race", Race, 1, lengthFlags), propertyFlags)),
|
|
|
};
|
|
|
SetProperties(properties);
|
|
|
|
|
|
var symbols = new SymbolDictionary(1)
|
|
|
{
|
|
|
[GlobalSymbolRegistry.Species] = new GetSetPropertyDescriptor(
|
|
|
- get: new ClrFunctionInstance(_engine, "get [Symbol.species]", (thisObj, _) => thisObj, 0,
|
|
|
- PropertyFlag.Configurable),
|
|
|
+ get: new ClrFunctionInstance(_engine, "get [Symbol.species]", (thisObj, _) => thisObj, 0, PropertyFlag.Configurable),
|
|
|
set: Undefined, PropertyFlag.Configurable)
|
|
|
};
|
|
|
SetSymbols(symbols);
|
|
@@ -157,34 +150,22 @@ namespace Jint.Native.Promise
|
|
|
return instance;
|
|
|
}
|
|
|
|
|
|
- // https://tc39.es/ecma262/#sec-promise.all
|
|
|
- // The all function returns a new promise which is fulfilled with an array of fulfillment values for the passed promises,
|
|
|
- // or rejects with the reason of the first passed promise that rejects. It resolves all elements of the passed iterable to promises as it runs this algorithm.
|
|
|
- //
|
|
|
- // 1. Let C be the this value.
|
|
|
- // 2. Let promiseCapability be ? NewPromiseCapability(C).
|
|
|
- // 3. Let promiseResolve be GetPromiseResolve(C).
|
|
|
- // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
|
|
- // 5. Let iteratorRecord be GetIterator(iterable).
|
|
|
- // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
|
|
- // 7. Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve).
|
|
|
- // 8. If result is an abrupt completion, then
|
|
|
- // a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
|
|
|
- // b. IfAbruptRejectPromise(result, promiseCapability).
|
|
|
- // 9. Return Completion(result)
|
|
|
- private JsValue All(JsValue thisObj, JsValue[] arguments)
|
|
|
+ // 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
|
|
|
+ private bool TryGetPromiseCapabilityAndIterator(JsValue thisObj, JsValue[] arguments, string callerName, out PromiseCapability capability, out ICallable promiseResolve, out IteratorInstance iterator)
|
|
|
{
|
|
|
if (!thisObj.IsObject())
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(_realm, "Promise.all called on non-object");
|
|
|
+ ExceptionHelper.ThrowTypeError(_realm, $"{callerName} called on non-object");
|
|
|
}
|
|
|
|
|
|
//2. Let promiseCapability be ? NewPromiseCapability(C).
|
|
|
- var (resultingPromise, resolve, reject, _, rejectObj) = NewPromiseCapability(_engine, thisObj);
|
|
|
+ capability = NewPromiseCapability(_engine, thisObj);
|
|
|
+ var reject = capability.Reject;
|
|
|
|
|
|
//3. Let promiseResolve be GetPromiseResolve(C).
|
|
|
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
|
|
- ICallable promiseResolve;
|
|
|
try
|
|
|
{
|
|
|
promiseResolve = GetPromiseResolve(thisObj);
|
|
@@ -192,11 +173,12 @@ namespace Jint.Native.Promise
|
|
|
catch (JavaScriptException e)
|
|
|
{
|
|
|
reject.Call(Undefined, new[] { e.Error });
|
|
|
- return resultingPromise;
|
|
|
+ promiseResolve = null!;
|
|
|
+ iterator = null!;
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
- IteratorInstance iterator;
|
|
|
// 5. Let iteratorRecord be GetIterator(iterable).
|
|
|
// 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
|
|
|
|
@@ -204,7 +186,7 @@ namespace Jint.Native.Promise
|
|
|
{
|
|
|
if (arguments.Length == 0)
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(_realm, "no arguments were passed to Promise.all");
|
|
|
+ ExceptionHelper.ThrowTypeError(_realm, $"no arguments were passed to {callerName}");
|
|
|
}
|
|
|
|
|
|
var iterable = arguments.At(0);
|
|
@@ -214,9 +196,21 @@ namespace Jint.Native.Promise
|
|
|
catch (JavaScriptException e)
|
|
|
{
|
|
|
reject.Call(Undefined, new[] { e.Error });
|
|
|
- return resultingPromise;
|
|
|
+ iterator = null!;
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // https://tc39.es/ecma262/#sec-promise.all
|
|
|
+ private JsValue All(JsValue thisObj, JsValue[] arguments)
|
|
|
+ {
|
|
|
+ if (!TryGetPromiseCapabilityAndIterator(thisObj, arguments, "Promise.all", out var capability, out var promiseResolve, out var iterator))
|
|
|
+ return capability.PromiseInstance;
|
|
|
+
|
|
|
+ var (resultingPromise, resolve, reject, _, rejectObj) = capability;
|
|
|
+
|
|
|
var results = new List<JsValue>();
|
|
|
bool doneIterating = false;
|
|
|
|
|
@@ -271,13 +265,13 @@ namespace Jint.Native.Promise
|
|
|
{
|
|
|
var capturedIndex = index;
|
|
|
|
|
|
- var fulfilled = false;
|
|
|
+ var alreadyCalled = false;
|
|
|
var onSuccess =
|
|
|
new ClrFunctionInstance(_engine, "", (_, args) =>
|
|
|
{
|
|
|
- if (!fulfilled)
|
|
|
+ if (!alreadyCalled)
|
|
|
{
|
|
|
- fulfilled = true;
|
|
|
+ alreadyCalled = true;
|
|
|
results[capturedIndex] = args.At(0);
|
|
|
ResolveIfFinished();
|
|
|
}
|
|
@@ -305,52 +299,132 @@ namespace Jint.Native.Promise
|
|
|
return resultingPromise;
|
|
|
}
|
|
|
|
|
|
- // https://tc39.es/ecma262/#sec-promise.any
|
|
|
- private JsValue Any(JsValue thisObj, JsValue[] arguments)
|
|
|
+ // https://tc39.es/ecma262/#sec-promise.allsettled
|
|
|
+ private JsValue AllSettled(JsValue thisObj, JsValue[] arguments)
|
|
|
{
|
|
|
- if (!thisObj.IsObject())
|
|
|
- {
|
|
|
- ExceptionHelper.ThrowTypeError(_realm, "Promise.any called on non-object");
|
|
|
- }
|
|
|
+ if (!TryGetPromiseCapabilityAndIterator(thisObj, arguments, "Promise.allSettled", out var capability, out var promiseResolve, out var iterator))
|
|
|
+ return capability.PromiseInstance;
|
|
|
|
|
|
- //2. Let promiseCapability be ? NewPromiseCapability(C).
|
|
|
- var (resultingPromise, resolve, reject, resolveObj, _) = NewPromiseCapability(_engine, thisObj);
|
|
|
+ var (resultingPromise, resolve, reject, _, rejectObj) = capability;
|
|
|
|
|
|
- //3. Let promiseResolve be GetPromiseResolve(C).
|
|
|
- // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
|
|
- ICallable promiseResolve;
|
|
|
- try
|
|
|
- {
|
|
|
- promiseResolve = GetPromiseResolve(thisObj);
|
|
|
- }
|
|
|
- catch (JavaScriptException e)
|
|
|
+ var results = new List<JsValue>();
|
|
|
+ bool doneIterating = false;
|
|
|
+
|
|
|
+ void ResolveIfFinished()
|
|
|
{
|
|
|
- reject.Call(Undefined, new[] { e.Error });
|
|
|
- return resultingPromise;
|
|
|
+ // that means all of them were resolved
|
|
|
+ // Note that "Undefined" is not null, thus the logic is sound, even though awkward
|
|
|
+ // also note that it is important to check if we are done iterating.
|
|
|
+ // if "then" method is sync then it will be resolved BEFORE the next iteration cycle
|
|
|
+ if (results.TrueForAll(static x => x != null) && doneIterating)
|
|
|
+ {
|
|
|
+ var array = _realm.Intrinsics.Array.ConstructFast(results);
|
|
|
+ resolve.Call(Undefined, new JsValue[] { array });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- IteratorInstance iterator;
|
|
|
- // 5. Let iteratorRecord be GetIterator(iterable).
|
|
|
- // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
|
|
-
|
|
|
+ // 27.2.4.1.2 PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )
|
|
|
+ // https://tc39.es/ecma262/#sec-performpromiseall
|
|
|
try
|
|
|
{
|
|
|
- if (arguments.Length == 0)
|
|
|
+ int index = 0;
|
|
|
+
|
|
|
+ do
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(_realm, "no arguments were passed to Promise.all");
|
|
|
- }
|
|
|
+ JsValue value;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (!iterator.TryIteratorStep(out var nextItem))
|
|
|
+ {
|
|
|
+ doneIterating = true;
|
|
|
|
|
|
- var iterable = arguments.At(0);
|
|
|
+ ResolveIfFinished();
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- iterator = iterable.GetIterator(_realm);
|
|
|
+ value = nextItem.Get(CommonProperties.Value);
|
|
|
+ }
|
|
|
+ catch (JavaScriptException e)
|
|
|
+ {
|
|
|
+ reject.Call(Undefined, new[] { e.Error });
|
|
|
+ return resultingPromise;
|
|
|
+ }
|
|
|
+
|
|
|
+ // note that null here is important
|
|
|
+ // it will help to detect if all inner promises were resolved
|
|
|
+ // In F# it would be Option<JsValue>
|
|
|
+ results.Add(null!);
|
|
|
+
|
|
|
+ var item = promiseResolve.Call(thisObj, new JsValue[] { value });
|
|
|
+ var thenProps = item.Get("then");
|
|
|
+ if (thenProps is ICallable thenFunc)
|
|
|
+ {
|
|
|
+ var capturedIndex = index;
|
|
|
+
|
|
|
+ var alreadyCalled = false;
|
|
|
+ var onSuccess =
|
|
|
+ new ClrFunctionInstance(_engine, "", (_, args) =>
|
|
|
+ {
|
|
|
+ if (!alreadyCalled)
|
|
|
+ {
|
|
|
+ alreadyCalled = true;
|
|
|
+
|
|
|
+ var res = Engine.Realm.Intrinsics.Object.Construct(2);
|
|
|
+ res.FastAddProperty("status", "fulfilled", true, true, true);
|
|
|
+ res.FastAddProperty("value", args.At(0), true, true, true);
|
|
|
+ results[capturedIndex] = res;
|
|
|
+
|
|
|
+ ResolveIfFinished();
|
|
|
+ }
|
|
|
+
|
|
|
+ return Undefined;
|
|
|
+ }, 1, PropertyFlag.Configurable);
|
|
|
+ var onFailure =
|
|
|
+ new ClrFunctionInstance(_engine, "", (_, args) =>
|
|
|
+ {
|
|
|
+ if (!alreadyCalled)
|
|
|
+ {
|
|
|
+ alreadyCalled = true;
|
|
|
+
|
|
|
+ var res = Engine.Realm.Intrinsics.Object.Construct(2);
|
|
|
+ res.FastAddProperty("status", "rejected", true, true, true);
|
|
|
+ res.FastAddProperty("reason", args.At(0), true, true, true);
|
|
|
+ results[capturedIndex] = res;
|
|
|
+
|
|
|
+ ResolveIfFinished();
|
|
|
+ }
|
|
|
+
|
|
|
+ return Undefined;
|
|
|
+ }, 1, PropertyFlag.Configurable);
|
|
|
+
|
|
|
+ thenFunc.Call(item, new JsValue[] { onSuccess, onFailure });
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowTypeError(_realm, "Passed non Promise-like value");
|
|
|
+ }
|
|
|
+
|
|
|
+ index += 1;
|
|
|
+ } while (true);
|
|
|
}
|
|
|
catch (JavaScriptException e)
|
|
|
{
|
|
|
+ iterator.Close(CompletionType.Throw);
|
|
|
reject.Call(Undefined, new[] { e.Error });
|
|
|
return resultingPromise;
|
|
|
}
|
|
|
|
|
|
+ return resultingPromise;
|
|
|
+ }
|
|
|
+
|
|
|
+ // https://tc39.es/ecma262/#sec-promise.any
|
|
|
+ private JsValue Any(JsValue thisObj, JsValue[] arguments)
|
|
|
+ {
|
|
|
+ if (!TryGetPromiseCapabilityAndIterator(thisObj, arguments, "Promise.any", out var capability, out var promiseResolve, out var iterator))
|
|
|
+ return capability.PromiseInstance;
|
|
|
+
|
|
|
+ var (resultingPromise, resolve, reject, resolveObj, _) = capability;
|
|
|
+
|
|
|
var errors = new List<JsValue>();
|
|
|
bool doneIterating = false;
|
|
|
|
|
@@ -405,14 +479,14 @@ namespace Jint.Native.Promise
|
|
|
{
|
|
|
var capturedIndex = index;
|
|
|
|
|
|
- var fulfilled = false;
|
|
|
+ var alreadyCalled = false;
|
|
|
|
|
|
var onError =
|
|
|
new ClrFunctionInstance(_engine, "", (_, args) =>
|
|
|
{
|
|
|
- if (!fulfilled)
|
|
|
+ if (!alreadyCalled)
|
|
|
{
|
|
|
- fulfilled = true;
|
|
|
+ alreadyCalled = true;
|
|
|
errors[capturedIndex] = args.At(0);
|
|
|
RejectIfAllRejected();
|
|
|
}
|
|
@@ -443,48 +517,11 @@ namespace Jint.Native.Promise
|
|
|
// https://tc39.es/ecma262/#sec-promise.race
|
|
|
private JsValue Race(JsValue thisObj, JsValue[] arguments)
|
|
|
{
|
|
|
- if (!thisObj.IsObject())
|
|
|
- {
|
|
|
- ExceptionHelper.ThrowTypeError(_realm, "Promise.all called on non-object");
|
|
|
- }
|
|
|
-
|
|
|
- // 2. Let promiseCapability be ? NewPromiseCapability(C).
|
|
|
- var (resultingPromise, resolve, reject, _, rejectObj) = NewPromiseCapability(_engine, thisObj);
|
|
|
-
|
|
|
- // 3. Let promiseResolve be GetPromiseResolve(C).
|
|
|
- // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
|
|
- ICallable promiseResolve;
|
|
|
- try
|
|
|
- {
|
|
|
- promiseResolve = GetPromiseResolve(thisObj);
|
|
|
- }
|
|
|
- catch (JavaScriptException e)
|
|
|
- {
|
|
|
- reject.Call(Undefined, new[] { e.Error });
|
|
|
- return resultingPromise;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- IteratorInstance iterator;
|
|
|
- // 5. Let iteratorRecord be GetIterator(iterable).
|
|
|
- // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
|
|
+ if (!TryGetPromiseCapabilityAndIterator(thisObj, arguments, "Promise.race", out var capability, out var promiseResolve, out var iterator))
|
|
|
+ return capability.PromiseInstance;
|
|
|
|
|
|
- try
|
|
|
- {
|
|
|
- if (arguments.Length == 0)
|
|
|
- {
|
|
|
- ExceptionHelper.ThrowTypeError(_realm, "no arguments were passed to Promise.all");
|
|
|
- }
|
|
|
+ var (resultingPromise, resolve, reject, _, rejectObj) = capability;
|
|
|
|
|
|
- var iterable = arguments.At(0);
|
|
|
-
|
|
|
- iterator = iterable.GetIterator(_realm);
|
|
|
- }
|
|
|
- catch (JavaScriptException e)
|
|
|
- {
|
|
|
- reject.Call(Undefined, new[] { e.Error });
|
|
|
- return resultingPromise;
|
|
|
- }
|
|
|
|
|
|
// 7. Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve).
|
|
|
// https://tc39.es/ecma262/#sec-performpromiserace
|